Merge "Hide NanoAppMessage setters" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 48d6392..8591a9c 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -77,6 +77,7 @@
         "camera_platform_flags_core_java_lib",
         "com.android.hardware.input-aconfig-java",
         "com.android.input.flags-aconfig-java",
+        "com.android.internal.compat.flags-aconfig-java",
         "com.android.internal.foldables.flags-aconfig-java",
         "com.android.internal.pm.pkg.component.flags-aconfig-java",
         "com.android.media.flags.bettertogether-aconfig-java",
@@ -663,6 +664,13 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+// Platform Compat
+java_aconfig_library {
+    name: "com.android.internal.compat.flags-aconfig-java",
+    aconfig_declarations: "compat_logging_flags",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Multi user
 aconfig_declarations {
     name: "android.multiuser.flags-aconfig",
diff --git a/apex/jobscheduler/service/Android.bp b/apex/jobscheduler/service/Android.bp
index 0104ee1..ace56d4 100644
--- a/apex/jobscheduler/service/Android.bp
+++ b/apex/jobscheduler/service/Android.bp
@@ -20,6 +20,7 @@
     ],
 
     libs: [
+        "androidx.annotation_annotation",
         "app-compat-annotations",
         "error_prone_annotations",
         "framework",
diff --git a/core/api/current.txt b/core/api/current.txt
index f5bd21b..173cf6c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4482,7 +4482,7 @@
     method @CallSuper public void onActionModeStarted(android.view.ActionMode);
     method public void onActivityReenter(int, android.content.Intent);
     method protected void onActivityResult(int, int, android.content.Intent);
-    method @FlaggedApi("android.security.content_uri_permission_apis") public void onActivityResult(int, int, @NonNull android.content.Intent, @NonNull android.app.ComponentCaller);
+    method @FlaggedApi("android.security.content_uri_permission_apis") public void onActivityResult(int, int, @Nullable android.content.Intent, @NonNull android.app.ComponentCaller);
     method @Deprecated public void onAttachFragment(android.app.Fragment);
     method public void onAttachedToWindow();
     method @Deprecated public void onBackPressed();
@@ -9602,8 +9602,8 @@
     method public static android.appwidget.AppWidgetManager getInstance(android.content.Context);
     method @FlaggedApi("android.appwidget.flags.generated_previews") @Nullable public android.widget.RemoteViews getWidgetPreview(@NonNull android.content.ComponentName, @Nullable android.os.UserHandle, int);
     method public boolean isRequestPinAppWidgetSupported();
-    method public void notifyAppWidgetViewDataChanged(int[], int);
-    method public void notifyAppWidgetViewDataChanged(int, int);
+    method @Deprecated public void notifyAppWidgetViewDataChanged(int[], int);
+    method @Deprecated public void notifyAppWidgetViewDataChanged(int, int);
     method public void partiallyUpdateAppWidget(int[], android.widget.RemoteViews);
     method public void partiallyUpdateAppWidget(int, android.widget.RemoteViews);
     method @FlaggedApi("android.appwidget.flags.generated_previews") public void removeWidgetPreview(@NonNull android.content.ComponentName, int);
@@ -19286,11 +19286,11 @@
     method @NonNull public java.util.List<java.lang.Integer> getSupportedExtensions();
     method public boolean isCaptureProcessProgressAvailable(int);
     method public boolean isPostviewAvailable(int);
-    field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Float>> EFV_PADDING_ZOOM_FACTOR_RANGE;
+    field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Float>> EFV_PADDING_ZOOM_FACTOR_RANGE;
     field public static final int EXTENSION_AUTOMATIC = 0; // 0x0
     field @Deprecated public static final int EXTENSION_BEAUTY = 1; // 0x1
     field public static final int EXTENSION_BOKEH = 2; // 0x2
-    field @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final int EXTENSION_EYES_FREE_VIDEOGRAPHY = 5; // 0x5
+    field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public static final int EXTENSION_EYES_FREE_VIDEOGRAPHY = 5; // 0x5
     field public static final int EXTENSION_FACE_RETOUCH = 1; // 0x1
     field public static final int EXTENSION_HDR = 3; // 0x3
     field public static final int EXTENSION_NIGHT = 4; // 0x4
@@ -19890,30 +19890,30 @@
     field public static final int MAX_THUMBNAIL_DIMENSION = 256; // 0x100
   }
 
-  @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class ExtensionCaptureRequest {
+  @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public final class ExtensionCaptureRequest {
     ctor public ExtensionCaptureRequest();
-    field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Boolean> EFV_AUTO_ZOOM;
-    field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_MAX_PADDING_ZOOM_FACTOR;
-    field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_PADDING_ZOOM_FACTOR;
-    field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_ROTATE_VIEWPORT;
-    field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> EFV_STABILIZATION_MODE;
-    field @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final int EFV_STABILIZATION_MODE_GIMBAL = 1; // 0x1
-    field @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final int EFV_STABILIZATION_MODE_LOCKED = 2; // 0x2
-    field @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final int EFV_STABILIZATION_MODE_OFF = 0; // 0x0
-    field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<android.util.Pair<java.lang.Integer,java.lang.Integer>> EFV_TRANSLATE_VIEWPORT;
+    field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Boolean> EFV_AUTO_ZOOM;
+    field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_MAX_PADDING_ZOOM_FACTOR;
+    field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_PADDING_ZOOM_FACTOR;
+    field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_ROTATE_VIEWPORT;
+    field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> EFV_STABILIZATION_MODE;
+    field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public static final int EFV_STABILIZATION_MODE_GIMBAL = 1; // 0x1
+    field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public static final int EFV_STABILIZATION_MODE_LOCKED = 2; // 0x2
+    field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public static final int EFV_STABILIZATION_MODE_OFF = 0; // 0x0
+    field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<android.util.Pair<java.lang.Integer,java.lang.Integer>> EFV_TRANSLATE_VIEWPORT;
   }
 
-  @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class ExtensionCaptureResult {
+  @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public final class ExtensionCaptureResult {
     ctor public ExtensionCaptureResult();
-    field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> EFV_AUTO_ZOOM;
-    field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<int[]> EFV_AUTO_ZOOM_PADDING_REGION;
-    field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_MAX_PADDING_ZOOM_FACTOR;
-    field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<int[]> EFV_PADDING_REGION;
-    field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_PADDING_ZOOM_FACTOR;
-    field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_ROTATE_VIEWPORT;
-    field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EFV_STABILIZATION_MODE;
-    field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.graphics.PointF[]> EFV_TARGET_COORDINATES;
-    field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.util.Pair<java.lang.Integer,java.lang.Integer>> EFV_TRANSLATE_VIEWPORT;
+    field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> EFV_AUTO_ZOOM;
+    field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<int[]> EFV_AUTO_ZOOM_PADDING_REGION;
+    field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_MAX_PADDING_ZOOM_FACTOR;
+    field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<int[]> EFV_PADDING_REGION;
+    field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_PADDING_ZOOM_FACTOR;
+    field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_ROTATE_VIEWPORT;
+    field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EFV_STABILIZATION_MODE;
+    field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.graphics.PointF[]> EFV_TARGET_COORDINATES;
+    field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.util.Pair<java.lang.Integer,java.lang.Integer>> EFV_TRANSLATE_VIEWPORT;
   }
 
   public class MultiResolutionImageReader implements java.lang.AutoCloseable {
@@ -57915,7 +57915,7 @@
     method public abstract boolean getBuiltInZoomControls();
     method public abstract int getCacheMode();
     method public abstract String getCursiveFontFamily();
-    method public abstract boolean getDatabaseEnabled();
+    method @Deprecated public abstract boolean getDatabaseEnabled();
     method @Deprecated public abstract String getDatabasePath();
     method public abstract int getDefaultFixedFontSize();
     method public abstract int getDefaultFontSize();
@@ -57961,7 +57961,7 @@
     method public abstract void setBuiltInZoomControls(boolean);
     method public abstract void setCacheMode(int);
     method public abstract void setCursiveFontFamily(String);
-    method public abstract void setDatabaseEnabled(boolean);
+    method @Deprecated public abstract void setDatabaseEnabled(boolean);
     method @Deprecated public abstract void setDatabasePath(String);
     method public abstract void setDefaultFixedFontSize(int);
     method public abstract void setDefaultFontSize(int);
@@ -60157,7 +60157,7 @@
     method public void setRadioGroupChecked(@IdRes int, @IdRes int);
     method public void setRelativeScrollPosition(@IdRes int, int);
     method @Deprecated public void setRemoteAdapter(int, @IdRes int, android.content.Intent);
-    method public void setRemoteAdapter(@IdRes int, android.content.Intent);
+    method @Deprecated public void setRemoteAdapter(@IdRes int, android.content.Intent);
     method public void setRemoteAdapter(@IdRes int, @NonNull android.widget.RemoteViews.RemoteCollectionItems);
     method public void setScrollPosition(@IdRes int, int);
     method public void setShort(@IdRes int, String, short);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index f7d9625..e0adee3 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 {
@@ -3348,7 +3337,7 @@
     field public static final int STATUS_SERVICE_UNAVAILABLE = 3; // 0x3
     field public static final int STATUS_SUCCESS = 1; // 0x1
     field public static final int STATUS_UNKNOWN = 0; // 0x0
-    field public static final int STATUS_UNSUPPORTED = 2; // 0x2
+    field @Deprecated public static final int STATUS_UNSUPPORTED = 2; // 0x2
     field @FlaggedApi("android.app.wearable.enable_data_request_observer_api") public static final int STATUS_UNSUPPORTED_DATA_TYPE = 8; // 0x8
     field @FlaggedApi("android.app.wearable.enable_unsupported_operation_status_code") public static final int STATUS_UNSUPPORTED_OPERATION = 6; // 0x6
     field public static final int STATUS_WEARABLE_UNAVAILABLE = 4; // 0x4
@@ -4912,7 +4901,6 @@
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int getImageFormat();
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public android.util.Size getSize();
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public android.view.Surface getSurface();
-    method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public void setColorSpace(int);
     method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public void setDynamicRangeProfile(long);
   }
 
@@ -4923,6 +4911,7 @@
 
   @FlaggedApi("com.android.internal.camera.flags.concert_mode") public class ExtensionConfiguration {
     ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public ExtensionConfiguration(int, int, @NonNull java.util.List<android.hardware.camera2.extension.ExtensionOutputConfiguration>, @Nullable android.hardware.camera2.CaptureRequest);
+    method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public void setColorSpace(int);
   }
 
   @FlaggedApi("com.android.internal.camera.flags.concert_mode") public class ExtensionOutputConfiguration {
@@ -6259,7 +6248,7 @@
     method public void addOnCompleteListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.radio.ProgramList.OnCompleteListener);
     method public void addOnCompleteListener(@NonNull android.hardware.radio.ProgramList.OnCompleteListener);
     method public void close();
-    method @Nullable public android.hardware.radio.RadioManager.ProgramInfo get(@NonNull android.hardware.radio.ProgramSelector.Identifier);
+    method @Deprecated @Nullable public android.hardware.radio.RadioManager.ProgramInfo get(@NonNull android.hardware.radio.ProgramSelector.Identifier);
     method @FlaggedApi("android.hardware.radio.hd_radio_improved") @NonNull public java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramInfos(@NonNull android.hardware.radio.ProgramSelector.Identifier);
     method public void registerListCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.radio.ProgramList.ListCallback);
     method public void registerListCallback(@NonNull android.hardware.radio.ProgramList.ListCallback);
@@ -6311,7 +6300,7 @@
     field @Deprecated public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5; // 0x5
     field @Deprecated public static final int IDENTIFIER_TYPE_DAB_SID_EXT = 5; // 0x5
     field public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10; // 0xa
-    field public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; // 0xb
+    field @Deprecated public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; // 0xb
     field public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9; // 0x9
     field public static final int IDENTIFIER_TYPE_HD_STATION_ID_EXT = 3; // 0x3
     field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int IDENTIFIER_TYPE_HD_STATION_LOCATION = 15; // 0xf
@@ -6319,8 +6308,8 @@
     field @Deprecated public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4; // 0x4
     field public static final int IDENTIFIER_TYPE_INVALID = 0; // 0x0
     field public static final int IDENTIFIER_TYPE_RDS_PI = 2; // 0x2
-    field public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd
-    field public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc
+    field @Deprecated public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd
+    field @Deprecated public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc
     field public static final int IDENTIFIER_TYPE_VENDOR_END = 1999; // 0x7cf
     field @Deprecated public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = 1999; // 0x7cf
     field @Deprecated public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = 1000; // 0x3e8
@@ -6373,7 +6362,7 @@
     field public static final int CONFIG_DAB_DAB_SOFT_LINKING = 8; // 0x8
     field public static final int CONFIG_DAB_FM_LINKING = 7; // 0x7
     field public static final int CONFIG_DAB_FM_SOFT_LINKING = 9; // 0x9
-    field public static final int CONFIG_FORCE_ANALOG = 2; // 0x2
+    field @Deprecated public static final int CONFIG_FORCE_ANALOG = 2; // 0x2
     field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int CONFIG_FORCE_ANALOG_AM = 11; // 0xb
     field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int CONFIG_FORCE_ANALOG_FM = 10; // 0xa
     field public static final int CONFIG_FORCE_DIGITAL = 3; // 0x3
@@ -12954,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/api/test-current.txt b/core/api/test-current.txt
index 892567c6..bc45a76 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -42,6 +42,7 @@
     field public static final String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE";
     field public static final String READ_WRITE_SYNC_DISABLED_MODE_CONFIG = "android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG";
     field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO";
+    field @FlaggedApi("android.permission.flags.sensitive_notification_app_protection") public static final String RECORD_SENSITIVE_CONTENT = "android.permission.RECORD_SENSITIVE_CONTENT";
     field public static final String REMAP_MODIFIER_KEYS = "android.permission.REMAP_MODIFIER_KEYS";
     field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
     field public static final String REQUEST_UNIQUE_ID_ATTESTATION = "android.permission.REQUEST_UNIQUE_ID_ATTESTATION";
diff --git a/core/java/Android.bp b/core/java/Android.bp
index ab1c9a4..4f96206 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -167,6 +167,9 @@
         "com/android/internal/logging/UiEventLoggerImpl.java",
         ":statslog-framework-java-gen",
     ],
+    libs: [
+        "androidx.annotation_annotation",
+    ],
     static_libs: ["modules-utils-uieventlogger-interface"],
 }
 
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index afbefca..1cc2d25 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -7473,7 +7473,7 @@
      *               intent.
      */
     @FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS)
-    public void onActivityResult(int requestCode, int resultCode, @NonNull Intent data,
+    public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data,
             @NonNull ComponentCaller caller) {
         onActivityResult(requestCode, resultCode, data);
     }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 1d39186..ae5cacd 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -974,6 +974,7 @@
         ContentCaptureOptions contentCaptureOptions;
 
         long[] disabledCompatChanges;
+        long[] mLoggableCompatChanges;
 
         SharedMemory mSerializedSystemFontMap;
 
@@ -1283,6 +1284,7 @@
                 AutofillOptions autofillOptions,
                 ContentCaptureOptions contentCaptureOptions,
                 long[] disabledCompatChanges,
+                long[] loggableCompatChanges,
                 SharedMemory serializedSystemFontMap,
                 long startRequestedElapsedTime,
                 long startRequestedUptime) {
@@ -1337,6 +1339,7 @@
             data.autofillOptions = autofillOptions;
             data.contentCaptureOptions = contentCaptureOptions;
             data.disabledCompatChanges = disabledCompatChanges;
+            data.mLoggableCompatChanges = loggableCompatChanges;
             data.mSerializedSystemFontMap = serializedSystemFontMap;
             data.startRequestedElapsedTime = startRequestedElapsedTime;
             data.startRequestedUptime = startRequestedUptime;
@@ -4073,6 +4076,13 @@
                     ActivityManager.getService().waitForNetworkStateUpdate(mNetworkBlockSeq);
                     mNetworkBlockSeq = INVALID_PROC_STATE_SEQ;
                 } catch (RemoteException ignored) {}
+                if (Flags.clearDnsCacheOnNetworkRulesUpdate()) {
+                    // InetAddress will cache UnknownHostException failures. If the rules got
+                    // updated and the app has network access now, we need to clear the negative
+                    // cache to ensure valid dns queries can work immediately.
+                    // TODO: b/329133769 - Clear only the negative cache once it is available.
+                    InetAddress.clearDnsCache();
+                }
             }
         }
     }
@@ -7123,7 +7133,7 @@
         Process.setStartTimes(SystemClock.elapsedRealtime(), SystemClock.uptimeMillis(),
                 data.startRequestedElapsedTime, data.startRequestedUptime);
 
-        AppCompatCallbacks.install(data.disabledCompatChanges);
+        AppCompatCallbacks.install(data.disabledCompatChanges, data.mLoggableCompatChanges);
         // Let libcore handle any compat changes after installing the list of compat changes.
         AppSpecializationHooks.handleCompatChangesBeforeBindingApplication();
 
diff --git a/core/java/android/app/AppCompatCallbacks.java b/core/java/android/app/AppCompatCallbacks.java
index 134cef5..f2debfc 100644
--- a/core/java/android/app/AppCompatCallbacks.java
+++ b/core/java/android/app/AppCompatCallbacks.java
@@ -30,41 +30,59 @@
  */
 public final class AppCompatCallbacks implements Compatibility.BehaviorChangeDelegate {
     private final long[] mDisabledChanges;
+    private final long[] mLoggableChanges;
     private final ChangeReporter mChangeReporter;
 
     /**
-     * Install this class into the current process.
+     * Install this class into the current process using the disabled and loggable changes lists.
      *
      * @param disabledChanges Set of compatibility changes that are disabled for this process.
+     * @param loggableChanges Set of compatibility changes that we want to log.
      */
-    public static void install(long[] disabledChanges) {
-        Compatibility.setBehaviorChangeDelegate(new AppCompatCallbacks(disabledChanges));
+    public static void install(long[] disabledChanges, long[] loggableChanges) {
+        Compatibility.setBehaviorChangeDelegate(
+                new AppCompatCallbacks(disabledChanges, loggableChanges));
     }
 
-    private AppCompatCallbacks(long[] disabledChanges) {
+    private AppCompatCallbacks(long[] disabledChanges, long[] loggableChanges) {
         mDisabledChanges = Arrays.copyOf(disabledChanges, disabledChanges.length);
+        mLoggableChanges = Arrays.copyOf(loggableChanges, loggableChanges.length);
         Arrays.sort(mDisabledChanges);
-        mChangeReporter = new ChangeReporter(
-                ChangeReporter.SOURCE_APP_PROCESS);
+        Arrays.sort(mLoggableChanges);
+        mChangeReporter = new ChangeReporter(ChangeReporter.SOURCE_APP_PROCESS);
+    }
+
+    /**
+     * Helper to determine if a list contains a changeId.
+     *
+     * @param list to search through
+     * @param changeId for which to search in the list
+     * @return true if the given changeId is found in the provided array.
+     */
+    private boolean changeIdInChangeList(long[] list, long changeId) {
+        return Arrays.binarySearch(list, changeId) >= 0;
     }
 
     public void onChangeReported(long changeId) {
-        reportChange(changeId, ChangeReporter.STATE_LOGGED);
+        boolean isLoggable = changeIdInChangeList(mLoggableChanges, changeId);
+        reportChange(changeId, ChangeReporter.STATE_LOGGED, isLoggable);
     }
 
     public boolean isChangeEnabled(long changeId) {
-        if (Arrays.binarySearch(mDisabledChanges, changeId) < 0) {
-            // Not present in the disabled array
-            reportChange(changeId, ChangeReporter.STATE_ENABLED);
+        boolean isEnabled = !changeIdInChangeList(mDisabledChanges, changeId);
+        boolean isLoggable = changeIdInChangeList(mLoggableChanges, changeId);
+        if (isEnabled) {
+            // Not present in the disabled changeId array
+            reportChange(changeId, ChangeReporter.STATE_ENABLED, isLoggable);
             return true;
         }
-        reportChange(changeId, ChangeReporter.STATE_DISABLED);
+        reportChange(changeId, ChangeReporter.STATE_DISABLED, isLoggable);
         return false;
     }
 
-    private void reportChange(long changeId, int state) {
+    private void reportChange(long changeId, int state, boolean isLoggable) {
         int uid = Process.myUid();
-        mChangeReporter.reportChange(uid, changeId, state);
+        mChangeReporter.reportChange(uid, changeId, state, isLoggable);
     }
 
 }
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index af56cb4..ed0c933 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -272,10 +272,17 @@
 
     @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;
@@ -288,7 +295,6 @@
 
     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.
@@ -340,21 +346,18 @@
      */
     private boolean mOwnsToken = false;
 
-    @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;
+    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;
 
     // The system service cache for the system services that are cached per-ContextImpl.
     @UnsupportedAppUsage
@@ -458,7 +461,7 @@
 
     @Override
     public void setTheme(int resId) {
-        synchronized (mSync) {
+        synchronized (mThemeLock) {
             if (mThemeResource != resId) {
                 mThemeResource = resId;
                 initializeTheme();
@@ -468,14 +471,14 @@
 
     @Override
     public int getThemeResId() {
-        synchronized (mSync) {
+        synchronized (mThemeLock) {
             return mThemeResource;
         }
     }
 
     @Override
     public Resources.Theme getTheme() {
-        synchronized (mSync) {
+        synchronized (mThemeLock) {
             if (mTheme != null) {
                 return mTheme;
             }
@@ -488,6 +491,7 @@
         }
     }
 
+    @GuardedBy("mThemeLock")
     private void initializeTheme() {
         if (mTheme == null) {
             mTheme = mResources.newTheme();
@@ -597,12 +601,18 @@
             if (sp == null) {
                 checkMode(mode);
                 if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) {
-                    if (isCredentialProtectedStorage()
-                            && !getSystemService(UserManager.class)
-                                    .isUserUnlockingOrUnlocked(UserHandle.myUserId())) {
-                        throw new IllegalStateException("SharedPreferences in credential encrypted "
-                                + "storage are not available until after user (id "
-                                + UserHandle.myUserId() + ") is unlocked");
+                    if (isCredentialProtectedStorage()) {
+                        final UserManager um = getSystemService(UserManager.class);
+                        if (um == null) {
+                            throw new IllegalStateException("SharedPreferences cannot be accessed "
+                                    + "if UserManager is not available. "
+                                    + "(e.g. from inside an isolated process)");
+                        }
+                        if (!um.isUserUnlockingOrUnlocked(UserHandle.myUserId())) {
+                            throw new IllegalStateException("SharedPreferences in "
+                                    + "credential encrypted storage are not available until after "
+                                    + "user (id " + UserHandle.myUserId() + ") is unlocked");
+                        }
                     }
                 }
                 sp = new SharedPreferencesImpl(file, mode);
@@ -731,12 +741,18 @@
 
     @UnsupportedAppUsage
     private File getPreferencesDir() {
-        synchronized (mSync) {
-            if (mPreferencesDir == null) {
-                mPreferencesDir = new File(getDataDir(), "shared_prefs");
+        File localPreferencesDir = mPreferencesDir;
+        if (localPreferencesDir == null) {
+            synchronized (mDirsLock) {
+                localPreferencesDir = mPreferencesDir;
+                if (localPreferencesDir == null) {
+                    localPreferencesDir = new File(getDataDir(), "shared_prefs");
+                    ensurePrivateDirExists(localPreferencesDir);
+                    mPreferencesDir = localPreferencesDir;
+                }
             }
-            return ensurePrivateDirExists(mPreferencesDir);
         }
+        return localPreferencesDir;
     }
 
     @Override
@@ -778,16 +794,16 @@
     /**
      * Common-path handling of app data dir creation
      */
-    private static File ensurePrivateDirExists(File file) {
-        return ensurePrivateDirExists(file, 0771, -1, null);
+    private static void ensurePrivateDirExists(File file) {
+        ensurePrivateDirExists(file, 0771, -1, null);
     }
 
-    private static File ensurePrivateCacheDirExists(File file, String xattr) {
+    private static void ensurePrivateCacheDirExists(File file, String xattr) {
         final int gid = UserHandle.getCacheAppGid(Process.myUid());
-        return ensurePrivateDirExists(file, 02771, gid, xattr);
+        ensurePrivateDirExists(file, 02771, gid, xattr);
     }
 
-    private static File ensurePrivateDirExists(File file, int mode, int gid, String xattr) {
+    private static void ensurePrivateDirExists(File file, int mode, int gid, String xattr) {
         if (!file.exists()) {
             final String path = file.getAbsolutePath();
             try {
@@ -815,17 +831,22 @@
                 }
             }
         }
-        return file;
     }
 
     @Override
     public File getFilesDir() {
-        synchronized (mSync) {
-            if (mFilesDir == null) {
-                mFilesDir = new File(getDataDir(), "files");
+        File localFilesDir = mFilesDir;
+        if (localFilesDir == null) {
+            localFilesDir = mFilesDir;
+            synchronized (mDirsLock) {
+                if (localFilesDir == null) {
+                    localFilesDir = new File(getDataDir(), "files");
+                    ensurePrivateDirExists(localFilesDir);
+                    mFilesDir = localFilesDir;
+                }
             }
-            return ensurePrivateDirExists(mFilesDir);
         }
+        return localFilesDir;
     }
 
     @Override
@@ -835,25 +856,37 @@
         final Path absoluteNormalizedCratePath = cratesRootPath.resolve(crateId)
                 .toAbsolutePath().normalize();
 
-        synchronized (mSync) {
-            if (mCratesDir == null) {
-                mCratesDir = cratesRootPath.toFile();
+        File localCratesDir = mCratesDir;
+        if (localCratesDir == null) {
+            synchronized (mDirsLock) {
+                localCratesDir = mCratesDir;
+                if (localCratesDir == null) {
+                    localCratesDir = cratesRootPath.toFile();
+                    ensurePrivateDirExists(localCratesDir);
+                    mCratesDir = localCratesDir;
+                }
             }
-            ensurePrivateDirExists(mCratesDir);
         }
 
-        File cratedDir = absoluteNormalizedCratePath.toFile();
-        return ensurePrivateDirExists(cratedDir);
+        File crateDir = absoluteNormalizedCratePath.toFile();
+        ensurePrivateDirExists(crateDir);
+        return crateDir;
     }
 
     @Override
     public File getNoBackupFilesDir() {
-        synchronized (mSync) {
-            if (mNoBackupFilesDir == null) {
-                mNoBackupFilesDir = new File(getDataDir(), "no_backup");
+        File localNoBackupFilesDir = mNoBackupFilesDir;
+        if (localNoBackupFilesDir == null) {
+            synchronized (mDirsLock) {
+                localNoBackupFilesDir = mNoBackupFilesDir;
+                if (localNoBackupFilesDir == null) {
+                    localNoBackupFilesDir = new File(getDataDir(), "no_backup");
+                    ensurePrivateDirExists(localNoBackupFilesDir);
+                    mNoBackupFilesDir = localNoBackupFilesDir;
+                }
             }
-            return ensurePrivateDirExists(mNoBackupFilesDir);
         }
+        return localNoBackupFilesDir;
     }
 
     @Override
@@ -865,13 +898,24 @@
 
     @Override
     public File[] getExternalFilesDirs(String type) {
-        synchronized (mSync) {
-            File[] dirs = Environment.buildExternalStorageAppFilesDirs(getPackageName());
-            if (type != null) {
-                dirs = Environment.buildPaths(dirs, 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;
+                }
             }
-            return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */);
         }
+        return localExternalFilesDirs;
     }
 
     @Override
@@ -883,30 +927,51 @@
 
     @Override
     public File[] getObbDirs() {
-        synchronized (mSync) {
-            File[] dirs = Environment.buildExternalStorageAppObbDirs(getPackageName());
-            return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */);
+        File[] localObbDirs = mObbDirs;
+        if (mObbDirs == null) {
+            synchronized (mDirsLock) {
+                localObbDirs = mObbDirs;
+                if (localObbDirs == null) {
+                    localObbDirs = Environment.buildExternalStorageAppObbDirs(getPackageName());
+                    localObbDirs = ensureExternalDirsExistOrFilter(
+                            localObbDirs, true /* tryCreateInProcess */);
+                    mObbDirs = localObbDirs;
+                }
+            }
         }
+        return localObbDirs;
     }
 
     @Override
     public File getCacheDir() {
-        synchronized (mSync) {
-            if (mCacheDir == null) {
-                mCacheDir = new File(getDataDir(), "cache");
+        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;
+                }
             }
-            return ensurePrivateCacheDirExists(mCacheDir, XATTR_INODE_CACHE);
         }
+        return localCacheDir;
     }
 
     @Override
     public File getCodeCacheDir() {
-        synchronized (mSync) {
-            if (mCodeCacheDir == null) {
-                mCodeCacheDir = getCodeCacheDirBeforeBind(getDataDir());
+        File localCodeCacheDir = mCodeCacheDir;
+        if (localCodeCacheDir == null) {
+            synchronized (mDirsLock) {
+                localCodeCacheDir = mCodeCacheDir;
+                if (localCodeCacheDir == null) {
+                    localCodeCacheDir = getCodeCacheDirBeforeBind(getDataDir());
+                    ensurePrivateCacheDirExists(localCodeCacheDir, XATTR_INODE_CODE_CACHE);
+                    mCodeCacheDir = localCodeCacheDir;
+                }
             }
-            return ensurePrivateCacheDirExists(mCodeCacheDir, XATTR_INODE_CODE_CACHE);
         }
+        return localCodeCacheDir;
     }
 
     /**
@@ -927,21 +992,37 @@
 
     @Override
     public File[] getExternalCacheDirs() {
-        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 */);
+        File[] localExternalCacheDirs = mExternalCacheDirs;
+        if (localExternalCacheDirs == null) {
+            synchronized (mDirsLock) {
+                localExternalCacheDirs = mExternalCacheDirs;
+                if (localExternalCacheDirs == null) {
+                    localExternalCacheDirs =
+                            Environment.buildExternalStorageAppCacheDirs(getPackageName());
+                    localExternalCacheDirs = ensureExternalDirsExistOrFilter(
+                            localExternalCacheDirs, false /* tryCreateInProcess */);
+                    mExternalCacheDirs = localExternalCacheDirs;
+                }
+            }
         }
+        return localExternalCacheDirs;
     }
 
     @Override
     public File[] getExternalMediaDirs() {
-        synchronized (mSync) {
-            File[] dirs = Environment.buildExternalStorageAppMediaDirs(getPackageName());
-            return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */);
+        File[] localExternalMediaDirs = mExternalMediaDirs;
+        if (localExternalMediaDirs == null) {
+            synchronized (mDirsLock) {
+                localExternalMediaDirs = mExternalMediaDirs;
+                if (localExternalMediaDirs == null) {
+                    localExternalMediaDirs = Environment.buildExternalStorageAppMediaDirs(getPackageName());
+                    localExternalMediaDirs = ensureExternalDirsExistOrFilter(
+                            localExternalMediaDirs, true /* tryCreateInProcess */);
+                    mExternalMediaDirs = localExternalMediaDirs;
+                }
+            }
         }
+        return localExternalMediaDirs;
     }
 
     /**
@@ -1040,16 +1121,22 @@
     }
 
     private File getDatabasesDir() {
-        synchronized (mSync) {
-            if (mDatabasesDir == null) {
-                if ("android".equals(getPackageName())) {
-                    mDatabasesDir = new File("/data/system");
-                } else {
-                    mDatabasesDir = new File(getDataDir(), "databases");
+        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;
                 }
             }
-            return ensurePrivateDirExists(mDatabasesDir);
         }
+        return localDatabasesDir;
     }
 
     @Override
diff --git a/core/java/android/app/GrammaticalInflectionManager.java b/core/java/android/app/GrammaticalInflectionManager.java
index 4ce983f..3e7d665 100644
--- a/core/java/android/app/GrammaticalInflectionManager.java
+++ b/core/java/android/app/GrammaticalInflectionManager.java
@@ -125,7 +125,10 @@
     /**
      * Get the current grammatical gender of privileged application from the encrypted file.
      *
-     * @return the value of grammatical gender
+     * @return the value of system grammatical gender only if the calling app has the permission,
+     * otherwise throwing an exception.
+     *
+     * @throws SecurityException if the caller does not have the required permission.
      *
      * @see Configuration#getGrammaticalGender
      */
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index a04620c..251e4e8 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -90,7 +90,7 @@
             in CompatibilityInfo compatInfo, in Map services,
             in Bundle coreSettings, in String buildSerial, in AutofillOptions autofillOptions,
             in ContentCaptureOptions contentCaptureOptions, in long[] disabledCompatChanges,
-            in SharedMemory serializedSystemFontMap,
+            in long[] loggableCompatChanges, in SharedMemory serializedSystemFontMap,
             long startRequestedElapsedTime, long startRequestedUptime);
     void runIsolatedEntryPoint(in String entryPoint, in String[] entryPointArgs);
     void scheduleExit();
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 79cb09d..dbdb70f 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();
         }
     }
 
@@ -7531,6 +7534,9 @@
         /**
          * Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is
          * attached to.
+         * <p>
+         * Note: Calling build() multiple times returns the same Notification instance,
+         * so reusing a builder to create multiple Notifications is discouraged.
          *
          * @return the fully constructed Notification.
          */
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/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index 986205a..9ef8b38 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -189,10 +189,13 @@
     @FlaggedApi(FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED)
     public static final int HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER = 2;
 
+    /**
+     * @hide
+     */
     @IntDef({HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED, HEADLESS_DEVICE_OWNER_MODE_AFFILIATED,
             HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER})
     @Retention(RetentionPolicy.SOURCE)
-    private @interface HeadlessDeviceOwnerMode {}
+    public @interface HeadlessDeviceOwnerMode {}
 
     /** @hide */
     public static class PolicyInfo {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 620bbaf..cb4ed058 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -53,6 +53,7 @@
 import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY;
 import static android.Manifest.permission.SET_TIME;
 import static android.Manifest.permission.SET_TIME_ZONE;
+import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
 import static android.app.admin.flags.Flags.FLAG_DEVICE_THEFT_API_ENABLED;
 import static android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED;
 import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED;
@@ -93,6 +94,7 @@
 import android.app.IServiceConnection;
 import android.app.KeyguardManager;
 import android.app.admin.SecurityLog.SecurityEvent;
+import android.app.admin.flags.Flags;
 import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
@@ -17526,4 +17528,25 @@
         }
         return -1;
     }
+
+    /**
+     * @return The headless device owner mode for the current set DO, returns
+     * {@link DeviceAdminInfo#HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED} if no DO is set.
+     *
+     * @hide
+     */
+    @DeviceAdminInfo.HeadlessDeviceOwnerMode
+    public int getHeadlessDeviceOwnerMode() {
+        if (!Flags.headlessDeviceOwnerProvisioningFixEnabled()) {
+            return HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
+        }
+        if (mService != null) {
+            try {
+                return mService.getHeadlessDeviceOwnerMode(mContext.getPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
+    }
 }
\ No newline at end of file
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 3a7a891c..03d0b0f 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -625,4 +625,6 @@
 
     void setMaxPolicyStorageLimit(String packageName, int storageLimit);
     int getMaxPolicyStorageLimit(String packageName);
+
+    int getHeadlessDeviceOwnerMode(String callerPackageName);
 }
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 1927019..c29ea6d 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -163,3 +163,13 @@
   description: "Enable UX changes for esim management"
   bug: "295301164"
 }
+
+flag {
+  name: "headless_device_owner_provisioning_fix_enabled"
+  namespace: "enterprise"
+  description: "Fix provisioning for single-user headless DO"
+  bug: "289515470"
+  metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/android/app/network-policy.aconfig b/core/java/android/app/network-policy.aconfig
new file mode 100644
index 0000000..88f386f
--- /dev/null
+++ b/core/java/android/app/network-policy.aconfig
@@ -0,0 +1,11 @@
+package: "android.app"
+
+flag {
+     namespace: "backstage_power"
+     name: "clear_dns_cache_on_network_rules_update"
+     description: "Clears the DNS cache when the network rules update"
+     bug: "237556596"
+     metadata {
+       purpose: PURPOSE_BUGFIX
+     }
+}
\ No newline at end of file
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 fd72c49..9573e6f 100644
--- a/core/java/android/app/wearable/WearableSensingManager.java
+++ b/core/java/android/app/wearable/WearableSensingManager.java
@@ -95,11 +95,12 @@
     /**
      * The value of the status code that indicates one or more of the requested events are not
      * supported.
+     *
+     * @deprecated WearableSensingManager does not deal with events. Use {@link
+     * STATUS_UNSUPPORTED_OPERATION} instead for operations not supported by the implementation of
+     * {@link WearableSensingService}.
      */
-    // TODO(b/324635656): Deprecate this status code. Update Javadoc:
-    // @deprecated WearableSensingManager does not deal with events. Use {@link
-    // STATUS_UNSUPPORTED_OPERATION} instead for operations not supported by the implementation of
-    // {@link WearableSensingService}.
+    @Deprecated
     public static final int STATUS_UNSUPPORTED = 2;
 
     /**
@@ -121,7 +122,6 @@
      * The value of the status code that indicates the method called is not supported by the
      * implementation of {@link WearableSensingService}.
      */
-
     @FlaggedApi(Flags.FLAG_ENABLE_UNSUPPORTED_OPERATION_STATUS_CODE)
     public static final int STATUS_UNSUPPORTED_OPERATION = 6;
 
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index cda4d89..2c0e035 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -822,7 +822,18 @@
      *
      * @param appWidgetIds  The AppWidget instances to notify of view data changes.
      * @param viewId        The collection view id.
+     * @deprecated The corresponding API
+     * {@link RemoteViews#setRemoteAdapter(int, Intent)} associated with this method has been
+     * deprecated. Moving forward please use
+     * {@link RemoteViews#setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)}
+     * instead to set {@link android.widget.RemoteViews.RemoteCollectionItems} for the remote
+     * adapter and update the widget views by calling {@link #updateAppWidget(int[], RemoteViews)},
+     * {@link #updateAppWidget(int, RemoteViews)},
+     * {@link #updateAppWidget(ComponentName, RemoteViews)},
+     * {@link #partiallyUpdateAppWidget(int[], RemoteViews)},
+     * or {@link #partiallyUpdateAppWidget(int, RemoteViews)}, whichever applicable.
      */
+    @Deprecated
     public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) {
         if (mService == null) {
             return;
@@ -873,7 +884,18 @@
      *
      * @param appWidgetId  The AppWidget instance to notify of view data changes.
      * @param viewId       The collection view id.
+     * @deprecated The corresponding API
+     * {@link RemoteViews#setRemoteAdapter(int, Intent)} associated with this method has been
+     * deprecated. Moving forward please use
+     * {@link RemoteViews#setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)}
+     * instead to set {@link android.widget.RemoteViews.RemoteCollectionItems} for the remote
+     * adapter and update the widget views by calling {@link #updateAppWidget(int[], RemoteViews)},
+     * {@link #updateAppWidget(int, RemoteViews)},
+     * {@link #updateAppWidget(ComponentName, RemoteViews)},
+     * {@link #partiallyUpdateAppWidget(int[], RemoteViews)},
+     * or {@link #partiallyUpdateAppWidget(int, RemoteViews)}, whichever applicable.
      */
+    @Deprecated
     public void notifyAppWidgetViewDataChanged(int appWidgetId, int viewId) {
         if (mService == null) {
             return;
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 8852705..bd04634 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -333,10 +333,9 @@
     }
 
     /**
-     * Specifies permissions necessary to launch this activity via
-     * {@link android.content.Context#startActivity} when passing content URIs. The default value is
-     * {@code none}, meaning no specific permissions are required. Setting this attribute restricts
-     * activity invocation based on the invoker's permissions.
+     * Specifies permissions necessary to launch this activity when passing content URIs. The
+     * default value is {@code none}, meaning no specific permissions are required. Setting this
+     * attribute restricts activity invocation based on the invoker's permissions.
      * @hide
      */
     @RequiredContentUriPermission
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index 533fa51..55957bf 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -133,4 +133,7 @@
     void setArchiveCompatibilityOptions(boolean enableIconOverlay, boolean enableUnarchivalConfirmation);
 
     List<UserHandle> getUserProfiles();
+
+    /** Saves view capture data to the wm trace directory. */
+    void saveViewCaptureData();
 }
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 41c1f17..3a5383d 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -1328,6 +1328,19 @@
     }
 
     /**
+     * Saves view capture data to the default location.
+     * @hide
+     */
+    @RequiresPermission(READ_FRAME_BUFFER)
+    public void saveViewCaptureData() {
+        try {
+            mService.saveViewCaptureData();
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
      * Unregister a callback, so that it won't be called when LauncherApps dumps.
      * @hide
      */
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index f660770..7fd0b03 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -38,7 +38,7 @@
     name: "nine_patch_frro"
     namespace: "resource_manager"
     description: "Feature flag for creating an frro from a 9-patch"
-    bug: "309232726"
+    bug: "296324826"
 }
 
 flag {
diff --git a/core/java/android/content/rollback/OWNERS b/core/java/android/content/rollback/OWNERS
index 8e5a0d8..c328b7c 100644
--- a/core/java/android/content/rollback/OWNERS
+++ b/core/java/android/content/rollback/OWNERS
@@ -1,5 +1,3 @@
 # Bug component: 819107
 
-ancr@google.com
-harshitmahajan@google.com
-robertogil@google.com
+include /services/core/java/com/android/server/crashrecovery/OWNERS
\ No newline at end of file
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/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index dc8f4b4..238c381 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -6098,7 +6098,7 @@
      * @hide
      */
     @ExtensionKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final Key<android.util.Range<Float>> EFV_PADDING_ZOOM_FACTOR_RANGE =
             new Key<android.util.Range<Float>>("android.efv.paddingZoomFactorRange", new TypeReference<android.util.Range<Float>>() {{ }});
 
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 749f218..6962811 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -142,7 +142,7 @@
     /**
      * An extension that aims to lock and stabilize a given region or object of interest.
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final int EXTENSION_EYES_FREE_VIDEOGRAPHY = 5;
 
     /**
@@ -180,6 +180,16 @@
             EXTENSION_HDR,
             EXTENSION_NIGHT};
 
+    /**
+     * List of synthetic CameraCharacteristics keys that are supported in the extensions.
+     */
+    private static final List<CameraCharacteristics.Key>
+            SUPPORTED_SYNTHETIC_CAMERA_CHARACTERISTICS =
+            Arrays.asList(
+                    CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES,
+                    CameraCharacteristics.REQUEST_AVAILABLE_COLOR_SPACE_PROFILES
+            );
+
     private final Context mContext;
     private final String mCameraId;
     private final Map<String, CameraCharacteristics> mCharacteristicsMap;
@@ -217,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;
     }
 
@@ -549,7 +563,7 @@
             public ExtensionConnectionManager() {
                 IntArray extensionList = new IntArray(EXTENSION_LIST.length);
                 extensionList.addAll(EXTENSION_LIST);
-                if (Flags.concertMode()) {
+                if (Flags.concertModeApi()) {
                     extensionList.add(EXTENSION_EYES_FREE_VIDEOGRAPHY);
                 }
 
@@ -752,7 +766,7 @@
 
         IntArray extensionList = new IntArray(EXTENSION_LIST.length);
         extensionList.addAll(EXTENSION_LIST);
-        if (Flags.concertMode()) {
+        if (Flags.concertModeApi()) {
             extensionList.add(EXTENSION_EYES_FREE_VIDEOGRAPHY);
         }
 
@@ -874,11 +888,17 @@
                 Class<CameraCharacteristics.Key<?>> keyTyped =
                         (Class<CameraCharacteristics.Key<?>>) key;
 
-                // Do not include synthetic keys. Including synthetic keys leads to undefined
-                // behavior. This causes inclusion of capabilities that may not be supported in
-                // camera extensions.
                 ret.addAll(chars.getAvailableKeyList(CameraCharacteristics.class, keyTyped, keys,
                         /*includeSynthetic*/ false));
+
+                // Add synthetic keys to the available key list if they are part of the supported
+                // synthetic camera characteristic key list
+                for (CameraCharacteristics.Key charKey :
+                        SUPPORTED_SYNTHETIC_CAMERA_CHARACTERISTICS) {
+                    if (chars.get(charKey) != null) {
+                        ret.add(charKey);
+                    }
+                }
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to query the extension for all available keys! Extension "
@@ -990,6 +1010,7 @@
                     case ImageFormat.YUV_420_888:
                     case ImageFormat.JPEG:
                     case ImageFormat.JPEG_R:
+                    case ImageFormat.YCBCR_P010:
                         break;
                     default:
                         throw new IllegalArgumentException("Unsupported format: " + format);
@@ -1021,8 +1042,9 @@
                     return generateJpegSupportedSizes(
                             extenders.second.getSupportedPostviewResolutions(sz),
                                     streamMap);
-                }  else if (format == ImageFormat.JPEG_R) {
-                    // Jpeg_R/UltraHDR is currently not supported in the basic extension case
+                }  else if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) {
+                    // Jpeg_R/UltraHDR + YCBCR_P010 is currently not supported in the basic
+                    // extension case
                     return new ArrayList<>();
                 } else {
                     throw new IllegalArgumentException("Unsupported format: " + format);
@@ -1118,16 +1140,16 @@
      *
      * <p>Device-specific extensions currently support at most three
      * multi-frame capture surface formats. ImageFormat.JPEG will be supported by all
-     * extensions while ImageFormat.YUV_420_888 and ImageFormat.JPEG_R may or may not be
-     * supported.</p>
+     * extensions while ImageFormat.YUV_420_888, ImageFormat.JPEG_R, or ImageFormat.YCBCR_P010
+     * may or may not be supported.</p>
      *
      * @param extension the extension type
      * @param format    device-specific extension output format
      * @return non-modifiable list of available sizes or an empty list if the format is not
      * supported.
      * @throws IllegalArgumentException in case of format different from ImageFormat.JPEG,
-     *                                  ImageFormat.YUV_420_888, ImageFormat.JPEG_R; or
-     *                                  unsupported extension.
+     *                                  ImageFormat.YUV_420_888, ImageFormat.JPEG_R,
+     *                                  ImageFormat.YCBCR_P010; or unsupported extension.
      */
     public @NonNull
     List<Size> getExtensionSupportedSizes(@Extension int extension, int format) {
@@ -1151,6 +1173,7 @@
                         case ImageFormat.YUV_420_888:
                         case ImageFormat.JPEG:
                         case ImageFormat.JPEG_R:
+                        case ImageFormat.YCBCR_P010:
                             break;
                         default:
                             throw new IllegalArgumentException("Unsupported format: " + format);
@@ -1183,8 +1206,9 @@
                         } else {
                             return generateSupportedSizes(null, format, streamMap);
                         }
-                    } else if (format == ImageFormat.JPEG_R) {
-                        // Jpeg_R/UltraHDR is currently not supported in the basic extension case
+                    } else if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) {
+                        // Jpeg_R/UltraHDR + YCBCR_P010 is currently not supported in the
+                        // basic extension case
                         return new ArrayList<>();
                     } else {
                         throw new IllegalArgumentException("Unsupported format: " + format);
@@ -1213,7 +1237,8 @@
      * @return the range of estimated minimal and maximal capture latency in milliseconds
      * or null if no capture latency info can be provided
      * @throws IllegalArgumentException in case of format different from {@link ImageFormat#JPEG},
-     *                                  {@link ImageFormat#YUV_420_888}, {@link ImageFormat#JPEG_R};
+     *                                  {@link ImageFormat#YUV_420_888}, {@link ImageFormat#JPEG_R}
+     *                                  {@link ImageFormat#YCBCR_P010};
      *                                  or unsupported extension.
      */
     public @Nullable Range<Long> getEstimatedCaptureLatencyRangeMillis(@Extension int extension,
@@ -1222,6 +1247,7 @@
             case ImageFormat.YUV_420_888:
             case ImageFormat.JPEG:
             case ImageFormat.JPEG_R:
+            case ImageFormat.YCBCR_P010:
                 //No op
                 break;
             default:
@@ -1269,8 +1295,8 @@
                     // specific and cannot be estimated accurately enough.
                     return  null;
                 }
-                if (format == ImageFormat.JPEG_R) {
-                    // JpegR/UltraHDR is not supported for basic extensions
+                if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) {
+                    // JpegR/UltraHDR + YCBCR_P010 is not supported for basic extensions
                     return null;
                 }
 
@@ -1522,7 +1548,7 @@
     @PublicKey
     @NonNull
     @ExtensionKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final Key<android.util.Range<Float>> EFV_PADDING_ZOOM_FACTOR_RANGE =
             CameraCharacteristics.EFV_PADDING_ZOOM_FACTOR_RANGE;
 }
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 9fb561b..7754e32 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -3905,7 +3905,7 @@
      * @see CaptureRequest#EFV_STABILIZATION_MODE
      * @hide
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final int EFV_STABILIZATION_MODE_OFF = 0;
 
     /**
@@ -3913,7 +3913,7 @@
      * @see CaptureRequest#EFV_STABILIZATION_MODE
      * @hide
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final int EFV_STABILIZATION_MODE_GIMBAL = 1;
 
     /**
@@ -3923,7 +3923,7 @@
      * @see CaptureRequest#EFV_STABILIZATION_MODE
      * @hide
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final int EFV_STABILIZATION_MODE_LOCKED = 2;
 
     //
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index c0db77c..13d5c7e 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -4335,7 +4335,7 @@
      * @hide
      */
     @ExtensionKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final Key<Float> EFV_PADDING_ZOOM_FACTOR =
             new Key<Float>("android.efv.paddingZoomFactor", float.class);
 
@@ -4358,7 +4358,7 @@
      * @hide
      */
     @ExtensionKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final Key<Boolean> EFV_AUTO_ZOOM =
             new Key<Boolean>("android.efv.autoZoom", boolean.class);
 
@@ -4379,7 +4379,7 @@
      * @hide
      */
     @ExtensionKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR =
             new Key<Float>("android.efv.maxPaddingZoomFactor", float.class);
 
@@ -4406,7 +4406,7 @@
      * @hide
      */
     @ExtensionKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final Key<Integer> EFV_STABILIZATION_MODE =
             new Key<Integer>("android.efv.stabilizationMode", int.class);
 
@@ -4428,7 +4428,7 @@
      * @hide
      */
     @ExtensionKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final Key<android.util.Pair<Integer,Integer>> EFV_TRANSLATE_VIEWPORT =
             new Key<android.util.Pair<Integer,Integer>>("android.efv.translateViewport", new TypeReference<android.util.Pair<Integer,Integer>>() {{ }});
 
@@ -4445,7 +4445,7 @@
      * @hide
      */
     @ExtensionKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final Key<Float> EFV_ROTATE_VIEWPORT =
             new Key<Float>("android.efv.rotateViewport", float.class);
 
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index a01c23d..7145501 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -5940,7 +5940,7 @@
      * @hide
      */
     @ExtensionKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final Key<int[]> EFV_PADDING_REGION =
             new Key<int[]>("android.efv.paddingRegion", int[].class);
 
@@ -5961,7 +5961,7 @@
      * @hide
      */
     @ExtensionKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final Key<int[]> EFV_AUTO_ZOOM_PADDING_REGION =
             new Key<int[]>("android.efv.autoZoomPaddingRegion", int[].class);
 
@@ -5984,7 +5984,7 @@
      * @hide
      */
     @ExtensionKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final Key<android.graphics.PointF[]> EFV_TARGET_COORDINATES =
             new Key<android.graphics.PointF[]>("android.efv.targetCoordinates", android.graphics.PointF[].class);
 
@@ -6014,7 +6014,7 @@
      * @hide
      */
     @ExtensionKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final Key<Float> EFV_PADDING_ZOOM_FACTOR =
             new Key<Float>("android.efv.paddingZoomFactor", float.class);
 
@@ -6041,7 +6041,7 @@
      * @hide
      */
     @ExtensionKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final Key<Integer> EFV_STABILIZATION_MODE =
             new Key<Integer>("android.efv.stabilizationMode", int.class);
 
@@ -6064,7 +6064,7 @@
      * @hide
      */
     @ExtensionKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final Key<Boolean> EFV_AUTO_ZOOM =
             new Key<Boolean>("android.efv.autoZoom", boolean.class);
 
@@ -6081,7 +6081,7 @@
      * @hide
      */
     @ExtensionKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final Key<Float> EFV_ROTATE_VIEWPORT =
             new Key<Float>("android.efv.rotateViewport", float.class);
 
@@ -6103,7 +6103,7 @@
      * @hide
      */
     @ExtensionKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final Key<android.util.Pair<Integer,Integer>> EFV_TRANSLATE_VIEWPORT =
             new Key<android.util.Pair<Integer,Integer>>("android.efv.translateViewport", new TypeReference<android.util.Pair<Integer,Integer>>() {{ }});
 
@@ -6124,7 +6124,7 @@
      * @hide
      */
     @ExtensionKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR =
             new Key<Float>("android.efv.maxPaddingZoomFactor", float.class);
 
diff --git a/core/java/android/hardware/camera2/ExtensionCaptureRequest.java b/core/java/android/hardware/camera2/ExtensionCaptureRequest.java
index 32039c6..c33956b 100644
--- a/core/java/android/hardware/camera2/ExtensionCaptureRequest.java
+++ b/core/java/android/hardware/camera2/ExtensionCaptureRequest.java
@@ -40,7 +40,7 @@
  * @see CaptureRequest
  * @see CameraExtensionSession
  */
-@FlaggedApi(Flags.FLAG_CONCERT_MODE)
+@FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
 public final class ExtensionCaptureRequest {
 
     /**
@@ -74,7 +74,7 @@
     @PublicKey
     @NonNull
     @ExtensionKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final Key<Float> EFV_PADDING_ZOOM_FACTOR = CaptureRequest.EFV_PADDING_ZOOM_FACTOR;
 
     /**
@@ -99,7 +99,7 @@
     @PublicKey
     @NonNull
     @ExtensionKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final Key<Boolean> EFV_AUTO_ZOOM = CaptureRequest.EFV_AUTO_ZOOM;
 
     /**
@@ -125,7 +125,7 @@
     @PublicKey
     @NonNull
     @ExtensionKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR = CaptureRequest.EFV_MAX_PADDING_ZOOM_FACTOR;
 
     /**
@@ -152,7 +152,7 @@
     @PublicKey
     @NonNull
     @ExtensionKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final Key<Integer> EFV_STABILIZATION_MODE = CaptureRequest.EFV_STABILIZATION_MODE;
 
     /**
@@ -176,7 +176,7 @@
     @PublicKey
     @NonNull
     @ExtensionKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final Key<android.util.Pair<Integer,Integer>> EFV_TRANSLATE_VIEWPORT = CaptureRequest.EFV_TRANSLATE_VIEWPORT;
 
     /**
@@ -193,7 +193,7 @@
     @PublicKey
     @NonNull
     @ExtensionKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final Key<Float> EFV_ROTATE_VIEWPORT = CaptureRequest.EFV_ROTATE_VIEWPORT;
 
 
@@ -205,14 +205,14 @@
      * <p>No stabilization.</p>
      * @see ExtensionCaptureRequest#EFV_STABILIZATION_MODE
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final int EFV_STABILIZATION_MODE_OFF = CaptureRequest.EFV_STABILIZATION_MODE_OFF;
 
     /**
      * <p>Gimbal stabilization mode.</p>
      * @see ExtensionCaptureRequest#EFV_STABILIZATION_MODE
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final int EFV_STABILIZATION_MODE_GIMBAL = CaptureRequest.EFV_STABILIZATION_MODE_GIMBAL;
 
     /**
@@ -221,7 +221,7 @@
      * stabilization to directionally steady the target region.</p>
      * @see ExtensionCaptureRequest#EFV_STABILIZATION_MODE
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final int EFV_STABILIZATION_MODE_LOCKED = CaptureRequest.EFV_STABILIZATION_MODE_LOCKED;
 
-} 
\ No newline at end of file
+}
diff --git a/core/java/android/hardware/camera2/ExtensionCaptureResult.java b/core/java/android/hardware/camera2/ExtensionCaptureResult.java
index 5c99909..95feb2f 100644
--- a/core/java/android/hardware/camera2/ExtensionCaptureResult.java
+++ b/core/java/android/hardware/camera2/ExtensionCaptureResult.java
@@ -42,7 +42,7 @@
  * @see CaptureRequest
  * @see CameraExtensionSession
  */
-@FlaggedApi(Flags.FLAG_CONCERT_MODE)
+@FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
 public final class ExtensionCaptureResult {
 
    /**
@@ -66,7 +66,7 @@
     @PublicKey
     @NonNull
     @ExtensionKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final Key<int[]> EFV_PADDING_REGION = CaptureResult.EFV_PADDING_REGION;
 
     /**
@@ -90,7 +90,7 @@
     @PublicKey
     @NonNull
     @ExtensionKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final Key<int[]> EFV_AUTO_ZOOM_PADDING_REGION = CaptureResult.EFV_AUTO_ZOOM_PADDING_REGION;
 
     /**
@@ -113,7 +113,7 @@
     @PublicKey
     @NonNull
     @ExtensionKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final Key<android.graphics.PointF[]> EFV_TARGET_COORDINATES = CaptureResult.EFV_TARGET_COORDINATES;
 
     /**
@@ -147,7 +147,7 @@
     @PublicKey
     @NonNull
     @ExtensionKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final Key<Float> EFV_PADDING_ZOOM_FACTOR = CaptureResult.EFV_PADDING_ZOOM_FACTOR;
 
     /**
@@ -174,7 +174,7 @@
     @PublicKey
     @NonNull
     @ExtensionKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final Key<Integer> EFV_STABILIZATION_MODE = CaptureResult.EFV_STABILIZATION_MODE;
 
     /**
@@ -199,7 +199,7 @@
     @PublicKey
     @NonNull
     @ExtensionKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final Key<Boolean> EFV_AUTO_ZOOM = CaptureResult.EFV_AUTO_ZOOM;
 
     /**
@@ -216,7 +216,7 @@
     @PublicKey
     @NonNull
     @ExtensionKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final Key<Float> EFV_ROTATE_VIEWPORT = CaptureResult.EFV_ROTATE_VIEWPORT;
 
     /**
@@ -240,7 +240,7 @@
     @PublicKey
     @NonNull
     @ExtensionKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final Key<android.util.Pair<Integer,Integer>> EFV_TRANSLATE_VIEWPORT = CaptureResult.EFV_TRANSLATE_VIEWPORT;
 
     /**
@@ -266,7 +266,7 @@
     @PublicKey
     @NonNull
     @ExtensionKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
     public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR = CaptureResult.EFV_MAX_PADDING_ZOOM_FACTOR;
 
-}
\ No newline at end of file
+}
diff --git a/core/java/android/hardware/camera2/extension/AdvancedExtender.java b/core/java/android/hardware/camera2/extension/AdvancedExtender.java
index 4895f38..8fa09a8 100644
--- a/core/java/android/hardware/camera2/extension/AdvancedExtender.java
+++ b/core/java/android/hardware/camera2/extension/AdvancedExtender.java
@@ -61,7 +61,6 @@
     private CameraUsageTracker mCameraUsageTracker;
     private static final String TAG = "AdvancedExtender";
 
-
     /**
      * Initialize a camera extension advanced extender instance.
      *
@@ -263,6 +262,13 @@
      *
      * <p>For example, an extension may limit the zoom ratio range. In this case, an OEM can return
      * a new zoom ratio range for the key {@link CameraCharacteristics#CONTROL_ZOOM_RATIO_RANGE}.
+     *
+     * <p> Currently, the only synthetic keys supported for override are
+     * {@link CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES} and
+     * {@link CameraCharacteristics#REQUEST_AVAILABLE_COLOR_SPACE_PROFILES}. To enable them, an OEM
+     * should override the respective native keys
+     * {@link CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP} and
+     *  {@link CameraCharacteristics#REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP}.
      */
     @FlaggedApi(Flags.FLAG_CAMERA_EXTENSIONS_CHARACTERISTICS_GET)
     @NonNull
diff --git a/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl b/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl
index 509bcb8..5567bed 100644
--- a/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl
+++ b/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl
@@ -27,6 +27,7 @@
     int imageFormat;
     int capacity;
     long usage;
+    long dynamicRangeProfile;
 
     const int TYPE_SURFACE = 0;
     const int TYPE_IMAGEREADER = 1;
diff --git a/core/java/android/hardware/camera2/extension/CameraOutputSurface.java b/core/java/android/hardware/camera2/extension/CameraOutputSurface.java
index 53f56bc..001b794 100644
--- a/core/java/android/hardware/camera2/extension/CameraOutputSurface.java
+++ b/core/java/android/hardware/camera2/extension/CameraOutputSurface.java
@@ -133,15 +133,4 @@
             @DynamicRangeProfiles.Profile long dynamicRangeProfile) {
         mOutputSurface.dynamicRangeProfile = dynamicRangeProfile;
     }
-
-    /**
-     * Set the color space. The default colorSpace
-     * will be
-     * {@link android.hardware.camera2.params.ColorSpaceProfiles.UNSPECIFIED}
-     * unless explicitly set using this method.
-     */
-    @FlaggedApi(Flags.FLAG_EXTENSION_10_BIT)
-    public void setColorSpace(int colorSpace) {
-        mOutputSurface.colorSpace = colorSpace;
-    }
 }
diff --git a/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl b/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl
index 84ca2b6..9d46b55 100644
--- a/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl
+++ b/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl
@@ -25,4 +25,5 @@
     CameraMetadataNative sessionParameter;
     int sessionTemplateId;
     int sessionType;
+    int colorSpace = -1;
 }
diff --git a/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java b/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java
index 96c88e6..84b7a7f 100644
--- a/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java
+++ b/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java
@@ -22,6 +22,7 @@
 import android.annotation.SystemApi;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.ColorSpaceProfiles;
 import android.os.IBinder;
 
 import com.android.internal.camera.flags.Flags;
@@ -48,6 +49,7 @@
     private final int mSessionTemplateId;
     private final List<ExtensionOutputConfiguration> mOutputs;
     private final CaptureRequest mSessionParameters;
+    private int mColorSpace;
 
     /**
      * Initialize an extension configuration instance
@@ -72,6 +74,18 @@
         mSessionTemplateId = sessionTemplateId;
         mOutputs = outputs;
         mSessionParameters = sessionParams;
+        mColorSpace = ColorSpaceProfiles.UNSPECIFIED;
+    }
+
+    /**
+     * Set the color space using the ordinal value of a
+     * {@link android.graphics.ColorSpace.Named}.
+     * The default will be -1, indicating an unspecified ColorSpace,
+     * unless explicitly set using this method.
+     */
+    @FlaggedApi(Flags.FLAG_EXTENSION_10_BIT)
+    public void setColorSpace(int colorSpace) {
+        mColorSpace = colorSpace;
     }
 
     @FlaggedApi(Flags.FLAG_CONCERT_MODE)
@@ -84,6 +98,11 @@
         ret.sessionTemplateId = mSessionTemplateId;
         ret.sessionType = mSessionType;
         ret.outputConfigs = new ArrayList<>(mOutputs.size());
+        if (Flags.extension10Bit()) {
+            ret.colorSpace = mColorSpace;
+        } else {
+            ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED;
+        }
         for (ExtensionOutputConfiguration outputConfig : mOutputs) {
             ret.outputConfigs.add(outputConfig.getOutputConfig());
         }
diff --git a/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java b/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java
index 9dc6d7b..3a67d61 100644
--- a/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java
+++ b/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.hardware.camera2.params.DynamicRangeProfiles;
 
 import com.android.internal.camera.flags.Flags;
 
@@ -79,6 +80,11 @@
         config.outputId = new OutputConfigId();
         config.outputId.id = mOutputConfigId;
         config.surfaceGroupId = mSurfaceGroupId;
+        if (Flags.extension10Bit()) {
+            config.dynamicRangeProfile = surface.getDynamicRangeProfile();
+        } else {
+            config.dynamicRangeProfile = DynamicRangeProfiles.STANDARD;
+        }
     }
 
     @Nullable CameraOutputConfig getOutputConfig() {
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index a7d6caf..6d9b51cb 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.content.Context;
+import android.graphics.ColorSpace;
 import android.graphics.ImageFormat;
 import android.graphics.SurfaceTexture;
 import android.hardware.SyncFence;
@@ -49,6 +50,7 @@
 import android.hardware.camera2.extension.ParcelImage;
 import android.hardware.camera2.extension.ParcelTotalCaptureResult;
 import android.hardware.camera2.extension.Request;
+import android.hardware.camera2.params.ColorSpaceProfiles;
 import android.hardware.camera2.params.DynamicRangeProfiles;
 import android.hardware.camera2.params.ExtensionSessionConfiguration;
 import android.hardware.camera2.params.OutputConfiguration;
@@ -62,6 +64,7 @@
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.Size;
 import android.view.Surface;
@@ -97,6 +100,9 @@
     private Surface mClientRepeatingRequestSurface;
     private Surface mClientCaptureSurface;
     private Surface mClientPostviewSurface;
+    private OutputConfiguration mClientRepeatingRequestOutputConfig;
+    private OutputConfiguration mClientCaptureOutputConfig;
+    private OutputConfiguration mClientPostviewOutputConfig;
     private CameraCaptureSession mCaptureSession = null;
     private ISessionProcessorImpl mSessionProcessor = null;
     private final InitializeSessionHandler mInitializeHandler;
@@ -142,8 +148,19 @@
 
         for (OutputConfiguration c : config.getOutputConfigurations()) {
             if (c.getDynamicRangeProfile() != DynamicRangeProfiles.STANDARD) {
-                throw new IllegalArgumentException("Unsupported dynamic range profile: " +
-                        c.getDynamicRangeProfile());
+                if (Flags.extension10Bit() && Flags.cameraExtensionsCharacteristicsGet()) {
+                    DynamicRangeProfiles dynamicProfiles = extensionChars.get(
+                            config.getExtension(),
+                            CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES);
+                    if (dynamicProfiles == null || !dynamicProfiles.getSupportedProfiles()
+                            .contains(c.getDynamicRangeProfile())) {
+                        throw new IllegalArgumentException("Unsupported dynamic range profile: "
+                                + c.getDynamicRangeProfile());
+                    }
+                } else {
+                    throw new IllegalArgumentException("Unsupported dynamic range profile: "
+                            + c.getDynamicRangeProfile());
+                }
             }
             if (c.getStreamUseCase() !=
                     CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT) {
@@ -157,12 +174,26 @@
                 config.getExtension(), SurfaceTexture.class);
         Surface repeatingRequestSurface = CameraExtensionUtils.getRepeatingRequestSurface(
                 config.getOutputConfigurations(), supportedPreviewSizes);
+        OutputConfiguration repeatingRequestOutputConfig = null;
         if (repeatingRequestSurface != null) {
+            for (OutputConfiguration outputConfig : config.getOutputConfigurations()) {
+                if (outputConfig.getSurface() == repeatingRequestSurface) {
+                    repeatingRequestOutputConfig = outputConfig;
+                }
+            }
             suitableSurfaceCount++;
         }
 
         HashMap<Integer, List<Size>> supportedCaptureSizes = new HashMap<>();
-        for (int format : CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS) {
+
+        IntArray supportedCaptureOutputFormats =
+                new IntArray(CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.length);
+        supportedCaptureOutputFormats.addAll(
+                CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS);
+        if (Flags.extension10Bit()) {
+            supportedCaptureOutputFormats.add(ImageFormat.YCBCR_P010);
+        }
+        for (int format : supportedCaptureOutputFormats.toArray()) {
             List<Size> supportedSizes = extensionChars.getExtensionSupportedSizes(
                     config.getExtension(), format);
             if (supportedSizes != null) {
@@ -171,7 +202,13 @@
         }
         Surface burstCaptureSurface = CameraExtensionUtils.getBurstCaptureSurface(
                 config.getOutputConfigurations(), supportedCaptureSizes);
+        OutputConfiguration burstCaptureOutputConfig = null;
         if (burstCaptureSurface != null) {
+            for (OutputConfiguration outputConfig : config.getOutputConfigurations()) {
+                if (outputConfig.getSurface() == burstCaptureSurface) {
+                    burstCaptureOutputConfig = outputConfig;
+                }
+            }
             suitableSurfaceCount++;
         }
 
@@ -180,13 +217,14 @@
         }
 
         Surface postviewSurface = null;
+        OutputConfiguration postviewOutputConfig = config.getPostviewOutputConfiguration();
         if (burstCaptureSurface != null && config.getPostviewOutputConfiguration() != null) {
             CameraExtensionUtils.SurfaceInfo burstCaptureSurfaceInfo =
                     CameraExtensionUtils.querySurface(burstCaptureSurface);
             Size burstCaptureSurfaceSize =
                     new Size(burstCaptureSurfaceInfo.mWidth, burstCaptureSurfaceInfo.mHeight);
             HashMap<Integer, List<Size>> supportedPostviewSizes = new HashMap<>();
-            for (int format : CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS) {
+            for (int format : supportedCaptureOutputFormats.toArray()) {
                 List<Size> supportedSizesPostview = extensionChars.getPostviewSupportedSizes(
                         config.getExtension(), burstCaptureSurfaceSize, format);
                 if (supportedSizesPostview != null) {
@@ -207,8 +245,8 @@
         extender.init(cameraId, characteristicsMapNative);
 
         CameraAdvancedExtensionSessionImpl ret = new CameraAdvancedExtensionSessionImpl(ctx,
-                extender, cameraDevice, characteristicsMapNative, repeatingRequestSurface,
-                burstCaptureSurface, postviewSurface, config.getStateCallback(),
+                extender, cameraDevice, characteristicsMapNative, repeatingRequestOutputConfig,
+                burstCaptureOutputConfig, postviewOutputConfig, config.getStateCallback(),
                 config.getExecutor(), sessionId, token, config.getExtension());
 
         ret.mStatsAggregator.setClientName(ctx.getOpPackageName());
@@ -223,8 +261,9 @@
             @NonNull IAdvancedExtenderImpl extender,
             @NonNull CameraDeviceImpl cameraDevice,
             Map<String, CameraMetadataNative> characteristicsMap,
-            @Nullable Surface repeatingRequestSurface, @Nullable Surface burstCaptureSurface,
-            @Nullable Surface postviewSurface,
+            @Nullable OutputConfiguration repeatingRequestOutputConfig,
+            @Nullable OutputConfiguration burstCaptureOutputConfig,
+            @Nullable OutputConfiguration postviewOutputConfig,
             @NonNull StateCallback callback, @NonNull Executor executor,
             int sessionId,
             @NonNull IBinder token,
@@ -235,9 +274,18 @@
         mCharacteristicsMap = characteristicsMap;
         mCallbacks = callback;
         mExecutor = executor;
-        mClientRepeatingRequestSurface = repeatingRequestSurface;
-        mClientCaptureSurface = burstCaptureSurface;
-        mClientPostviewSurface = postviewSurface;
+        mClientRepeatingRequestOutputConfig = repeatingRequestOutputConfig;
+        mClientCaptureOutputConfig = burstCaptureOutputConfig;
+        mClientPostviewOutputConfig = postviewOutputConfig;
+        if (repeatingRequestOutputConfig != null) {
+            mClientRepeatingRequestSurface = repeatingRequestOutputConfig.getSurface();
+        }
+        if (burstCaptureOutputConfig != null) {
+            mClientCaptureSurface = burstCaptureOutputConfig.getSurface();
+        }
+        if (postviewOutputConfig != null) {
+            mClientPostviewSurface = postviewOutputConfig.getSurface();
+        }
         mHandlerThread = new HandlerThread(TAG);
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
@@ -262,9 +310,9 @@
             return;
         }
 
-        OutputSurface previewSurface = initializeParcelable(mClientRepeatingRequestSurface);
-        OutputSurface captureSurface = initializeParcelable(mClientCaptureSurface);
-        OutputSurface postviewSurface = initializeParcelable(mClientPostviewSurface);
+        OutputSurface previewSurface = initializeParcelable(mClientRepeatingRequestOutputConfig);
+        OutputSurface captureSurface = initializeParcelable(mClientCaptureOutputConfig);
+        OutputSurface postviewSurface = initializeParcelable(mClientPostviewOutputConfig);
 
         mSessionProcessor = mAdvancedExtender.getSessionProcessor();
         CameraSessionConfig sessionConfig = mSessionProcessor.initSession(mToken,
@@ -300,6 +348,23 @@
             cameraOutput.setTimestampBase(OutputConfiguration.TIMESTAMP_BASE_SENSOR);
             cameraOutput.setReadoutTimestampEnabled(false);
             cameraOutput.setPhysicalCameraId(output.physicalCameraId);
+            if (Flags.extension10Bit()) {
+                boolean validDynamicRangeProfile = false;
+                for (long profile = DynamicRangeProfiles.STANDARD;
+                        profile < DynamicRangeProfiles.PUBLIC_MAX; profile <<= 1) {
+                    if (output.dynamicRangeProfile == profile) {
+                        validDynamicRangeProfile = true;
+                        break;
+                    }
+                }
+                if (validDynamicRangeProfile) {
+                    cameraOutput.setDynamicRangeProfile(output.dynamicRangeProfile);
+                } else {
+                    Log.e(TAG, "Extension configured dynamic range profile "
+                            + output.dynamicRangeProfile
+                            + " is not valid, using default DynamicRangeProfile.STANDARD");
+                }
+            }
             outputList.add(cameraOutput);
             mCameraConfigMap.put(cameraOutput.getSurface(), output);
         }
@@ -314,7 +379,16 @@
         SessionConfiguration sessionConfiguration = new SessionConfiguration(sessionType,
                 outputList, new CameraExtensionUtils.HandlerExecutor(mHandler),
                 new SessionStateHandler());
-
+        if (Flags.extension10Bit()) {
+            if (sessionConfig.colorSpace >= 0
+                    && sessionConfig.colorSpace < ColorSpace.Named.values().length) {
+                sessionConfiguration.setColorSpace(
+                        ColorSpace.Named.values()[sessionConfig.colorSpace]);
+            } else {
+                Log.e(TAG, "Extension configured color space " + sessionConfig.colorSpace
+                        + " is not valid, using default unspecified color space");
+            }
+        }
         if ((sessionConfig.sessionParameter != null) &&
                 (!sessionConfig.sessionParameter.isEmpty())) {
             CaptureRequest.Builder requestBuilder = mCameraDevice.createCaptureRequest(
@@ -362,21 +436,38 @@
         return ret;
     }
 
-    private static OutputSurface initializeParcelable(Surface s) {
+    private static OutputSurface initializeParcelable(OutputConfiguration o) {
         OutputSurface ret = new OutputSurface();
-        if (s != null) {
+
+        if (o != null && o.getSurface() != null) {
+            Surface s = o.getSurface();
             ret.surface = s;
             ret.size = new android.hardware.camera2.extension.Size();
             Size surfaceSize = SurfaceUtils.getSurfaceSize(s);
             ret.size.width = surfaceSize.getWidth();
             ret.size.height = surfaceSize.getHeight();
             ret.imageFormat = SurfaceUtils.getSurfaceFormat(s);
+
+            if (Flags.extension10Bit()) {
+                ret.dynamicRangeProfile = o.getDynamicRangeProfile();
+                ColorSpace colorSpace = o.getColorSpace();
+                if (colorSpace != null) {
+                    ret.colorSpace = colorSpace.getId();
+                } else {
+                    ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED;
+                }
+            } else {
+                ret.dynamicRangeProfile = DynamicRangeProfiles.STANDARD;
+                ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED;
+            }
         } else {
             ret.surface = null;
             ret.size = new android.hardware.camera2.extension.Size();
             ret.size.width = -1;
             ret.size.height = -1;
             ret.imageFormat = ImageFormat.UNKNOWN;
+            ret.dynamicRangeProfile = DynamicRangeProfiles.STANDARD;
+            ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED;
         }
 
         return ret;
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 725b413..5b32f33 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -58,12 +58,15 @@
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.Pair;
 import android.util.Size;
 import android.view.Surface;
 
+import com.android.internal.camera.flags.Flags;
+
 import java.io.Closeable;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -183,7 +186,14 @@
         }
 
         HashMap<Integer, List<Size>> supportedCaptureSizes = new HashMap<>();
-        for (int format : CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS) {
+        IntArray supportedCaptureOutputFormats =
+                new IntArray(CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.length);
+        supportedCaptureOutputFormats.addAll(
+                CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS);
+        if (Flags.extension10Bit()) {
+            supportedCaptureOutputFormats.add(ImageFormat.YCBCR_P010);
+        }
+        for (int format : supportedCaptureOutputFormats.toArray()) {
             List<Size> supportedSizes = extensionChars.getExtensionSupportedSizes(
                     config.getExtension(), format);
             if (supportedSizes != null) {
@@ -207,7 +217,7 @@
             Size burstCaptureSurfaceSize =
                     new Size(burstCaptureSurfaceInfo.mWidth, burstCaptureSurfaceInfo.mHeight);
             HashMap<Integer, List<Size>> supportedPostviewSizes = new HashMap<>();
-            for (int format : CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS) {
+            for (int format : supportedCaptureOutputFormats.toArray()) {
                 List<Size> supportedSizesPostview = extensionChars.getPostviewSupportedSizes(
                         config.getExtension(), burstCaptureSurfaceSize, format);
                 if (supportedSizesPostview != null) {
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
index a8066aa..f0c6e2e 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
@@ -29,10 +29,13 @@
 import android.media.Image;
 import android.media.ImageWriter;
 import android.os.Handler;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.Size;
 import android.view.Surface;
 
+import com.android.internal.camera.flags.Flags;
+
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -130,9 +133,16 @@
     public static Surface getBurstCaptureSurface(
             @NonNull List<OutputConfiguration> outputConfigs,
             @NonNull HashMap<Integer, List<Size>> supportedCaptureSizes) {
+        IntArray supportedCaptureOutputFormats =
+                new IntArray(CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.length);
+        supportedCaptureOutputFormats.addAll(
+                CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS);
+        if (Flags.extension10Bit()) {
+            supportedCaptureOutputFormats.add(ImageFormat.YCBCR_P010);
+        }
         for (OutputConfiguration config : outputConfigs) {
             SurfaceInfo surfaceInfo = querySurface(config.getSurface());
-            for (int supportedFormat : SUPPORTED_CAPTURE_OUTPUT_FORMATS) {
+            for (int supportedFormat : supportedCaptureOutputFormats.toArray()) {
                 if (surfaceInfo.mFormat == supportedFormat) {
                     Size captureSize = new Size(surfaceInfo.mWidth, surfaceInfo.mHeight);
                     if (supportedCaptureSizes.containsKey(supportedFormat)) {
diff --git a/core/java/android/hardware/devicestate/DeviceState.java b/core/java/android/hardware/devicestate/DeviceState.java
index 905d911..76888f3 100644
--- a/core/java/android/hardware/devicestate/DeviceState.java
+++ b/core/java/android/hardware/devicestate/DeviceState.java
@@ -543,11 +543,13 @@
                 int identifier = source.readInt();
                 String name = source.readString8();
                 ArraySet<@DeviceStateProperties Integer> systemProperties = new ArraySet<>();
-                for (int i = 0; i < source.readInt(); i++) {
+                int systemPropertySize = source.readInt();
+                for (int i = 0; i < systemPropertySize; i++) {
                     systemProperties.add(source.readInt());
                 }
                 ArraySet<@DeviceStateProperties Integer> physicalProperties = new ArraySet<>();
-                for (int j = 0; j < source.readInt(); j++) {
+                int physicalPropertySize = source.readInt();
+                for (int j = 0; j < physicalPropertySize; j++) {
                     physicalProperties.add(source.readInt());
                 }
                 return new DeviceState.Configuration(identifier, name, systemProperties,
diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java
index a3a2a2e..c5167db 100644
--- a/core/java/android/hardware/radio/ProgramList.java
+++ b/core/java/android/hardware/radio/ProgramList.java
@@ -304,7 +304,11 @@
      *
      * @param id primary identifier of a program to fetch
      * @return the program info, or null if there is no such program on the list
+     *
+     * @deprecated Use {@link #getProgramInfos(ProgramSelector.Identifier)} to get all programs
+     * with the given primary identifier
      */
+    @Deprecated
     public @Nullable RadioManager.ProgramInfo get(@NonNull ProgramSelector.Identifier id) {
         Map<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries;
         synchronized (mLock) {
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index a968c6f..0740374 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -312,15 +312,23 @@
     public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10;
     /**
      * 1: AM, 2:FM
+     * @deprecated use {@link #IDENTIFIER_TYPE_DRMO_FREQUENCY} instead
      */
+    @Deprecated
     public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11;
     /**
      * 32bit primary identifier for SiriusXM Satellite Radio.
+     *
+     * @deprecated SiriusXM Satellite Radio is not supported
      */
+    @Deprecated
     public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12;
     /**
      * 0-999 range
+     *
+     * @deprecated SiriusXM Satellite Radio is not supported
      */
+    @Deprecated
     public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13;
     /**
      * 44bit compound primary identifier for Digital Audio Broadcasting and
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 61cf8901..da6c686 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -166,7 +166,12 @@
      * analog handover state managed from the HAL implementation side.
      *
      * <p>Some radio technologies may not support this, i.e. DAB.
+     *
+     * @deprecated Use {@link #CONFIG_FORCE_ANALOG_FM} instead. If {@link #CONFIG_FORCE_ANALOG_FM}
+     * is supported in HAL, {@link RadioTuner#setConfigFlag} and {@link RadioTuner#isConfigFlagSet}
+     * with CONFIG_FORCE_ANALOG will set/get the value of {@link #CONFIG_FORCE_ANALOG_FM}.
      */
+    @Deprecated
     public static final int CONFIG_FORCE_ANALOG = 2;
     /**
      * Forces the digital playback for the supporting radio technology.
diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl
index 09e6b5d..c489c58 100644
--- a/core/java/android/service/dreams/IDreamManager.aidl
+++ b/core/java/android/service/dreams/IDreamManager.aidl
@@ -38,7 +38,6 @@
     boolean isDreaming();
     @UnsupportedAppUsage
     boolean isDreamingOrInPreview();
-    @UnsupportedAppUsage
     boolean canStartDreaming(boolean isScreenOn);
     void finishSelf(in IBinder token, boolean immediate);
     void startDozing(in IBinder token, int screenState, int screenBrightness);
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/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index a08264e..ccc17ec 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -819,19 +819,15 @@
         /**
          * Called when the keyphrase is spoken.
          *
-         * <p>This implicitly stops listening for the keyphrase once it's detected. Clients should
-         * start a recognition again once they are done handling this detection.
+         * <p>If {@code eventPayload.isRecognitionStopped()} returns true, this implicitly stops
+         * listening for the keyphrase once it's detected. Clients should start a recognition again
+         * once they are done handling this detection.
          *
          * @param eventPayload Payload data for the detection event. This may contain the trigger
          *     audio, if requested when calling {@link
-         *     AlwaysOnHotwordDetector#startRecognition(int)}.
+         *     AlwaysOnHotwordDetector#startRecognition(int)} or if the audio comes from the {@link
+         *     android.service.wearable.WearableSensingService}.
          */
-        // TODO(b/324635656): Update Javadoc for 24Q3 release:
-        // 1. Prepend to the first paragraph:
-        //     If {@code eventPayload.isRecognitionStopped()} returns true, this...
-        // 2. Append to the description for @param eventPayload:
-        //     ...or if the audio comes from {@link
-        //     android.service.wearable.WearableSensingService}.
         public abstract void onDetected(@NonNull EventPayload eventPayload);
 
         /**
diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java
index 60e9de7..937aecc 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -363,9 +363,11 @@
      * {@link HotwordDetector#startRecognition(ParcelFileDescriptor, AudioFormat,
      * PersistableBundle)} run} hotword recognition on audio coming from an external connected
      * microphone.
-     * <p>
-     * Upon invoking the {@code callback}, the system closes {@code audioStream} and sends the
-     * detection result to the {@link HotwordDetector.Callback hotword detector}.
+     *
+     * <p>Upon invoking the {@code callback}, the system will send the detection result to
+     * the {@link HotwordDetector}'s callback. If {@code
+     * options.getBoolean(KEY_SYSTEM_WILL_CLOSE_AUDIO_STREAM_AFTER_CALLBACK, true)} returns true,
+     * the system will also close the {@code audioStream} after {@code callback} is invoked.
      *
      * @param audioStream Stream containing audio bytes returned from a microphone
      * @param audioFormat Format of the supplied audio
@@ -375,11 +377,6 @@
      * PersistableBundle)}.
      * @param callback The callback to use for responding to the detection request.
      */
-    // TODO(b/324635656): Update Javadoc for 24Q3 release. Change the last paragraph to:
-    // <p>Upon invoking the {@code callback}, the system will send the detection result to
-    // the {@link HotwordDetector}'s callback. If {@code
-    // options.getBoolean(KEY_SYSTEM_WILL_CLOSE_AUDIO_STREAM_AFTER_CALLBACK, true)} returns true,
-    // the system will also close the {@code audioStream} after {@code callback} is invoked.
     public void onDetect(
             @NonNull ParcelFileDescriptor audioStream,
             @NonNull AudioFormat audioFormat,
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/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index f68fcab9..aff1d4a 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -119,3 +119,10 @@
   is_fixed_read_only: true
   bug: "324676775"
 }
+
+flag {
+  name: "handwriting_cursor_position"
+  namespace: "text"
+  description: "When handwriting is initiated in an unfocused TextView, cursor is placed at the end of the closest paragraph."
+  bug: "323376217"
+}
diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java
index a6724da..a4c3ed9 100644
--- a/core/java/android/util/apk/ApkSignatureVerifier.java
+++ b/core/java/android/util/apk/ApkSignatureVerifier.java
@@ -24,6 +24,7 @@
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 import static android.util.apk.ApkSignatureSchemeV4Verifier.APK_SIGNATURE_SCHEME_DEFAULT;
 
+import android.annotation.NonNull;
 import android.content.pm.Signature;
 import android.content.pm.SigningDetails;
 import android.content.pm.SigningDetails.SignatureSchemeVersion;
@@ -33,9 +34,12 @@
 import android.os.Build;
 import android.os.Trace;
 import android.os.incremental.V4Signature;
+import android.util.ArrayMap;
 import android.util.Pair;
+import android.util.Slog;
 import android.util.jar.StrictJarFile;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.ArrayUtils;
 
 import libcore.io.IoUtils;
@@ -63,8 +67,14 @@
  */
 public class ApkSignatureVerifier {
 
+    private static final String LOG_TAG = "ApkSignatureVerifier";
+
     private static final AtomicReference<byte[]> sBuffer = new AtomicReference<>();
 
+    @GuardedBy("sOverrideSigningDetails")
+    private static final ArrayMap<SigningDetails, SigningDetails> sOverrideSigningDetails =
+            new ArrayMap<>();
+
     /**
      * Verifies the provided APK and returns the certificates associated with each signer.
      */
@@ -95,7 +105,54 @@
         if (result.isError()) {
             return input.error(result);
         }
-        return input.success(result.getResult().signingDetails);
+        SigningDetails signingDetails = result.getResult().signingDetails;
+        if (Build.isDebuggable()) {
+            SigningDetails overrideSigningDetails;
+            synchronized (sOverrideSigningDetails) {
+                overrideSigningDetails = sOverrideSigningDetails.get(signingDetails);
+            }
+            if (overrideSigningDetails != null) {
+                Slog.i(LOG_TAG, "Applying override signing details for APK " + apkPath);
+                signingDetails = overrideSigningDetails;
+            }
+        }
+        return input.success(signingDetails);
+    }
+
+    /**
+     * Add a pair of signing details so that packages signed with {@code oldSigningDetails} will
+     * behave as if they are signed by the {@code newSigningDetails}.
+     *
+     * @param oldSigningDetails the original signing detail of the package
+     * @param newSigningDetails the new signing detail that will replace the original one
+     */
+    public static void addOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails,
+            @NonNull SigningDetails newSigningDetails) {
+        synchronized (sOverrideSigningDetails) {
+            sOverrideSigningDetails.put(oldSigningDetails, newSigningDetails);
+        }
+    }
+
+    /**
+     * Remove a pair of signing details previously added via {@link #addOverrideSigningDetails} by
+     * the old signing details.
+     *
+     * @param oldSigningDetails the original signing detail of the package
+     * @throws SecurityException if the build is not debuggable
+     */
+    public static void removeOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails) {
+        synchronized (sOverrideSigningDetails) {
+            sOverrideSigningDetails.remove(oldSigningDetails);
+        }
+    }
+
+    /**
+     * Clear all pairs of signing details previously added via {@link #addOverrideSigningDetails}.
+     */
+    public static void clearOverrideSigningDetails() {
+        synchronized (sOverrideSigningDetails) {
+            sOverrideSigningDetails.clear();
+        }
     }
 
     /**
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index 66655fc..29c8350 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import static com.android.text.flags.Flags.handwritingCursorPosition;
+
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -557,7 +559,8 @@
     }
 
     private void requestFocusWithoutReveal(View view) {
-        if (view instanceof EditText editText && !mState.mStylusDownWithinEditorBounds) {
+        if (!handwritingCursorPosition() && view instanceof EditText editText
+                && !mState.mStylusDownWithinEditorBounds) {
             // If the stylus down point was inside the EditText's bounds, then the EditText will
             // automatically set its cursor position nearest to the stylus down point when it
             // gains focus. If the stylus down point was outside the EditText's bounds (within
@@ -576,6 +579,17 @@
         } else {
             view.requestFocus();
         }
+        if (handwritingCursorPosition() && view instanceof EditText editText) {
+            // Move the cursor to the end of the paragraph closest to the stylus down point.
+            view.getLocationInWindow(mTempLocation);
+            int line = editText.getLineAtCoordinate(mState.mStylusDownY - mTempLocation[1]);
+            int paragraphEnd = TextUtils.indexOf(editText.getText(), '\n',
+                    editText.getLayout().getLineStart(line));
+            if (paragraphEnd < 0) {
+                paragraphEnd = editText.getText().length();
+            }
+            editText.setSelection(paragraphEnd);
+        }
     }
 
     /**
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/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java
index 1acfc1b..644a7a9 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -233,6 +233,28 @@
     public static final String DEVICE_CONFIG_INCLUDE_INVISIBLE_VIEW_GROUP_IN_ASSIST_STRUCTURE =
             "include_invisible_view_group_in_assist_structure";
 
+    /**
+     * Bugfix flag, Autofill should ignore views resetting to empty states.
+     *
+     * See frameworks/base/services/autofill/bugfixes.aconfig#ignore_view_state_reset_to_empty
+     * for more information.
+     *
+     * @hide
+     */
+    public static final String DEVICE_CONFIG_IGNORE_VIEW_STATE_RESET_TO_EMPTY =
+            "ignore_view_state_reset_to_empty";
+
+    /**
+     * Bugfix flag, Autofill should ignore view updates if an Auth intent is showing.
+     *
+     * See frameworks/base/services/autofill/bugfixes.aconfig#relayout
+     * for more information.
+     *
+     * @hide
+     */
+    public static final String DEVICE_CONFIG_IGNORE_RELAYOUT_WHEN_AUTH_PENDING =
+            "ignore_relayout_auth_pending";
+
     // END AUTOFILL FOR ALL APPS FLAGS //
 
 
@@ -494,6 +516,22 @@
                 false);
     }
 
+    /** @hide */
+    public static boolean shouldIgnoreViewStateResetToEmpty() {
+        return DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_AUTOFILL,
+                DEVICE_CONFIG_IGNORE_VIEW_STATE_RESET_TO_EMPTY,
+                false);
+    }
+
+    /** @hide */
+    public static boolean shouldIgnoreRelayoutWhenAuthPending() {
+        return DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_AUTOFILL,
+                DEVICE_CONFIG_IGNORE_RELAYOUT_WHEN_AUTH_PENDING,
+                false);
+    }
+
     /**
      * Whether should enable multi-line filter
      *
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 1484bfb..e15baae 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -974,7 +974,7 @@
         mShouldIncludeInvisibleViewInAssistStructure =
                 AutofillFeatureFlags.shouldIncludeInvisibleViewInAssistStructure();
 
-        mRelayoutFix = Flags.relayout();
+        mRelayoutFix = AutofillFeatureFlags.shouldIgnoreRelayoutWhenAuthPending();
         mIsCredmanIntegrationEnabled = Flags.autofillCredmanIntegration();
     }
 
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 14c5348..d12eda3 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1203,7 +1203,11 @@
      * changes to this setting after that point.
      *
      * @param flag {@code true} if the WebView should use the database storage API
+     * @deprecated WebSQL is deprecated and this method will become a no-op on all
+     * Android versions once support is removed in Chromium. See
+     * https://developer.chrome.com/blog/deprecating-web-sql for more information.
      */
+    @Deprecated
     public abstract void setDatabaseEnabled(boolean flag);
 
     /**
@@ -1236,7 +1240,11 @@
      *
      * @return {@code true} if the database storage API is enabled
      * @see #setDatabaseEnabled
+     * @deprecated WebSQL is deprecated and this method will become a no-op on all
+     * Android versions once support is removed in Chromium. See
+     * https://developer.chrome.com/blog/deprecating-web-sql for more information.
      */
+    @Deprecated
     public abstract boolean getDatabaseEnabled();
 
     /**
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/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index a2d8d80..e3caf70 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -5127,7 +5127,10 @@
      * @param viewId The id of the {@link AdapterView}
      * @param intent The intent of the service which will be
      *            providing data to the RemoteViewsAdapter
+     * @deprecated use
+     * {@link #setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)} instead
      */
+    @Deprecated
     public void setRemoteAdapter(@IdRes int viewId, Intent intent) {
         if (remoteAdapterConversion()) {
             addAction(new SetRemoteCollectionItemListAdapterAction(viewId, intent));
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 1721462..0373539 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -15490,8 +15490,9 @@
         return x;
     }
 
+    /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    int getLineAtCoordinate(float y) {
+    public int getLineAtCoordinate(float y) {
         y -= getTotalPaddingTop();
         // Clamp the position to inside of the view.
         y = Math.max(0.0f, y);
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/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index ce74848..82e613e 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -77,4 +77,12 @@
     description: "Properties to allow apps and activities to opt-in to cover display rendering"
     bug: "312530526"
     is_fixed_read_only: true
+}
+
+flag {
+    namespace: "windowing_sdk"
+    name: "enable_wm_extensions_for_all_flag"
+    description: "Whether to enable WM Extensions for all devices"
+    bug: "306666082"
+    is_fixed_read_only: true
 }
\ No newline at end of file
diff --git a/core/java/com/android/internal/accessibility/common/MagnificationConstants.java b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
index 2c49303..2db3e65 100644
--- a/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
+++ b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.accessibility.common;
 
+import android.os.SystemProperties;
+
 /**
  * Collection of common constants for accessibility magnification.
  */
@@ -31,6 +33,7 @@
     /** Minimum supported value for magnification scale. */
     public static final float SCALE_MIN_VALUE = 1.0f;
 
-    /** Maximum supported value for magnification scale. */
-    public static final float SCALE_MAX_VALUE = 8.0f;
+    /** Maximum supported value for magnification scale. Default of 8.0. */
+    public static final float SCALE_MAX_VALUE =
+            Float.parseFloat(SystemProperties.get("ro.config.max_magnification_scale", "8.0"));
 }
diff --git a/core/java/com/android/internal/compat/Android.bp b/core/java/com/android/internal/compat/Android.bp
new file mode 100644
index 0000000..9ff05a6
--- /dev/null
+++ b/core/java/com/android/internal/compat/Android.bp
@@ -0,0 +1,7 @@
+aconfig_declarations {
+    name: "compat_logging_flags",
+    package: "com.android.internal.compat.flags",
+    srcs: [
+        "compat_logging_flags.aconfig",
+    ],
+}
diff --git a/core/java/com/android/internal/compat/ChangeReporter.java b/core/java/com/android/internal/compat/ChangeReporter.java
index b9d3df6..6ff546f 100644
--- a/core/java/com/android/internal/compat/ChangeReporter.java
+++ b/core/java/com/android/internal/compat/ChangeReporter.java
@@ -24,6 +24,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.compat.flags.Flags;
 import com.android.internal.util.FrameworkStatsLog;
 
 import java.lang.annotation.Retention;
@@ -40,7 +41,7 @@
  * @hide
  */
 public final class ChangeReporter {
-    private static final String TAG = "CompatibilityChangeReporter";
+    private static final String TAG = "CompatChangeReporter";
     private int mSource;
 
     private static final class ChangeReport {
@@ -84,19 +85,34 @@
      * Report the change to stats log and to the debug log if the change was not previously
      * logged already.
      *
+     * @param uid             affected by the change
+     * @param changeId        the reported change id
+     * @param state           of the reported change - enabled/disabled/only logged
+     * @param isLoggableBySdk whether debug logging is allowed for this change based on target
+     *                        SDK version. This is combined with other logic to determine whether to
+     *                        actually log. If the sdk version does not matter, should be true.
+     */
+    public void reportChange(int uid, long changeId, int state, boolean isLoggableBySdk) {
+        if (shouldWriteToStatsLog(uid, changeId, state)) {
+            FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED, uid,
+                    changeId, state, mSource);
+        }
+        if (shouldWriteToDebug(uid, changeId, state, isLoggableBySdk)) {
+            debugLog(uid, changeId, state);
+        }
+        markAsReported(uid, new ChangeReport(changeId, state));
+    }
+
+    /**
+     * Report the change to stats log and to the debug log if the change was not previously
+     * logged already.
+     *
      * @param uid      affected by the change
      * @param changeId the reported change id
      * @param state    of the reported change - enabled/disabled/only logged
      */
     public void reportChange(int uid, long changeId, int state) {
-        if (shouldWriteToStatsLog(uid, changeId, state)) {
-            FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED, uid,
-                    changeId, state, mSource);
-        }
-        if (shouldWriteToDebug(uid, changeId, state)) {
-            debugLog(uid, changeId, state);
-        }
-        markAsReported(uid, new ChangeReport(changeId, state));
+        reportChange(uid, changeId, state, true);
     }
 
     /**
@@ -130,14 +146,43 @@
     /**
      * Returns whether the next report should be logged to logcat.
      *
-     * @param uid      affected by the change
-     * @param changeId the reported change id
-     * @param state    of the reported change - enabled/disabled/only logged
+     * @param uid             affected by the change
+     * @param changeId        the reported change id
+     * @param state           of the reported change - enabled/disabled/only logged
+     * @param isLoggableBySdk whether debug logging is allowed for this change based on target
+     *                        SDK version. This is combined with other logic to determine whether to
+     *                        actually log. If the sdk version does not matter, should be true.
+     * @return true if the report should be logged
+     */
+    @VisibleForTesting
+    public boolean shouldWriteToDebug(
+            int uid, long changeId, int state, boolean isLoggableBySdk) {
+        // If log all bit is on, always return true.
+        if (mDebugLogAll) return true;
+        // If the change has already been reported, do not write.
+        if (isAlreadyReported(uid, new ChangeReport(changeId, state))) return false;
+
+        // If the flag is turned off or the TAG's logging is forced to debug level with
+        // `adb setprop log.tag.CompatChangeReporter=DEBUG`, write to debug since the above checks
+        // have already passed.
+        boolean skipLoggingFlag = Flags.skipOldAndDisabledCompatLogging();
+        if (!skipLoggingFlag || Log.isLoggable(TAG, Log.DEBUG)) return true;
+
+        // Log if the change is enabled and targets the latest sdk version.
+        return isLoggableBySdk && state != STATE_DISABLED;
+    }
+
+    /**
+     * Returns whether the next report should be logged to logcat.
+     *
+     * @param uid         affected by the change
+     * @param changeId    the reported change id
+     * @param state       of the reported change - enabled/disabled/only logged
      * @return true if the report should be logged
      */
     @VisibleForTesting
     public boolean shouldWriteToDebug(int uid, long changeId, int state) {
-        return mDebugLogAll || !isAlreadyReported(uid, new ChangeReport(changeId, state));
+        return shouldWriteToDebug(uid, changeId, state, true);
     }
 
     private boolean isAlreadyReported(int uid, ChangeReport report) {
diff --git a/core/java/com/android/internal/compat/compat_logging_flags.aconfig b/core/java/com/android/internal/compat/compat_logging_flags.aconfig
new file mode 100644
index 0000000..fab3856
--- /dev/null
+++ b/core/java/com/android/internal/compat/compat_logging_flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.internal.compat.flags"
+
+flag {
+    name: "skip_old_and_disabled_compat_logging"
+    namespace: "platform_compat"
+    description: "Feature flag for skipping debug logging for changes that do not target the latest sdk or are disabled"
+    bug: "323949942"
+    is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index cbe0700..d4dcec9 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -93,6 +93,9 @@
             throw ex;
         }
 
+        if (peer.getUid() != Process.SYSTEM_UID) {
+            throw new ZygoteSecurityException("Only system UID is allowed to connect to Zygote.");
+        }
         isEof = false;
     }
 
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_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/com_android_internal_os_ZygoteCommandBuffer.cpp b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
index 54c4cd5..e0cc055 100644
--- a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
+++ b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
@@ -354,6 +354,18 @@
   return result;
 }
 
+static uid_t getSocketPeerUid(int socket, const std::function<void(const std::string&)>& fail_fn) {
+  struct ucred credentials;
+  socklen_t cred_size = sizeof credentials;
+  if (getsockopt(socket, SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1
+      || cred_size != sizeof credentials) {
+    fail_fn(CREATE_ERROR("Failed to get socket credentials, %s",
+                         strerror(errno)));
+  }
+
+  return credentials.uid;
+}
+
 // Read all lines from the current command into the buffer, and then reset the buffer, so
 // we will start reading again at the beginning of the command, starting with the argument
 // count. And we don't need access to the fd to do so.
@@ -413,19 +425,12 @@
     fail_fn_z("Failed to retrieve session socket timeout");
   }
 
-  struct ucred credentials;
-  socklen_t cred_size = sizeof credentials;
-  if (getsockopt(n_buffer->getFd(), SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1
-      || cred_size != sizeof credentials) {
-    fail_fn_1(CREATE_ERROR("ForkRepeatedly failed to get initial credentials, %s",
-                           strerror(errno)));
+  uid_t peerUid = getSocketPeerUid(session_socket, fail_fn_1);
+  if (peerUid != static_cast<uid_t>(expected_uid)) {
+    return JNI_FALSE;
   }
-
   bool first_time = true;
   do {
-    if (credentials.uid != static_cast<uid_t>(expected_uid)) {
-      return JNI_FALSE;
-    }
     n_buffer->readAllLines(first_time ? fail_fn_1 : fail_fn_n);
     n_buffer->reset();
     int pid = zygote::forkApp(env, /* no pipe FDs */ -1, -1, session_socket_fds,
@@ -453,6 +458,7 @@
       }
     }
     for (;;) {
+      bool valid_session_socket = true;
       // Clear buffer and get count from next command.
       n_buffer->clear();
       // Poll isn't strictly necessary for now. But without it, disconnect is hard to detect.
@@ -463,25 +469,50 @@
       if ((fd_structs[SESSION_IDX].revents & POLLIN) != 0) {
         if (n_buffer->getCount(fail_fn_z) != 0) {
           break;
-        }  // else disconnected;
+        } else {
+          // Session socket was disconnected
+          valid_session_socket = false;
+          close(session_socket);
+        }
       } else if (poll_res == 0 || (fd_structs[ZYGOTE_IDX].revents & POLLIN) == 0) {
         fail_fn_z(
             CREATE_ERROR("Poll returned with no descriptors ready! Poll returned %d", poll_res));
       }
-      // We've now seen either a disconnect or connect request.
-      close(session_socket);
-      int new_fd = TEMP_FAILURE_RETRY(accept(zygote_socket_fd, nullptr, nullptr));
+      int new_fd = -1;
+      do {
+        // We've now seen either a disconnect or connect request.
+        new_fd = TEMP_FAILURE_RETRY(accept(zygote_socket_fd, nullptr, nullptr));
+        if (new_fd == -1) {
+          fail_fn_z(CREATE_ERROR("Accept(%d) failed: %s", zygote_socket_fd, strerror(errno)));
+        }
+        uid_t newPeerUid = getSocketPeerUid(new_fd, fail_fn_1);
+        if (newPeerUid != static_cast<uid_t>(expected_uid)) {
+          ALOGW("Dropping new connection with a mismatched uid %d\n", newPeerUid);
+          close(new_fd);
+          new_fd = -1;
+        } else {
+          // If we still have a valid session socket, close it now
+          if (valid_session_socket) {
+              close(session_socket);
+          }
+          valid_session_socket = true;
+        }
+      } while (!valid_session_socket);
+
+      // At this point we either have a valid new connection (new_fd > 0), or
+      // an existing session socket we can poll on
       if (new_fd == -1) {
-        fail_fn_z(CREATE_ERROR("Accept(%d) failed: %s", zygote_socket_fd, strerror(errno)));
+        // The new connection wasn't valid, and we still have an old one; retry polling
+        continue;
       }
       if (new_fd != session_socket) {
-          // Move new_fd back to the old value, so that we don't have to change Java-level data
-          // structures to reflect a change. This implicitly closes the old one.
-          if (TEMP_FAILURE_RETRY(dup2(new_fd, session_socket)) != session_socket) {
-            fail_fn_z(CREATE_ERROR("Failed to move fd %d to %d: %s",
-                                   new_fd, session_socket, strerror(errno)));
-          }
-          close(new_fd);  //  On Linux, fd is closed even if EINTR is returned.
+        // Move new_fd back to the old value, so that we don't have to change Java-level data
+        // structures to reflect a change. This implicitly closes the old one.
+        if (TEMP_FAILURE_RETRY(dup2(new_fd, session_socket)) != session_socket) {
+          fail_fn_z(CREATE_ERROR("Failed to move fd %d to %d: %s",
+                                 new_fd, session_socket, strerror(errno)));
+        }
+        close(new_fd);  //  On Linux, fd is closed even if EINTR is returned.
       }
       // If we ever return, we effectively reuse the old Java ZygoteConnection.
       // None of its state needs to change.
@@ -493,13 +524,6 @@
         fail_fn_z(CREATE_ERROR("Failed to set send timeout for socket %d: %s",
                                session_socket, strerror(errno)));
       }
-      if (getsockopt(session_socket, SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1) {
-        fail_fn_z(CREATE_ERROR("ForkMany failed to get credentials: %s", strerror(errno)));
-      }
-      if (cred_size != sizeof credentials) {
-        fail_fn_z(CREATE_ERROR("ForkMany credential size = %d, should be %d",
-                               cred_size, static_cast<int>(sizeof credentials)));
-      }
     }
     first_time = false;
   } while (n_buffer->isSimpleForkCommand(minUid, fail_fn_n));
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/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1acdc75..8ea742d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6916,6 +6916,12 @@
     <permission android:name="android.permission.MANAGE_MEDIA_PROJECTION"
         android:protectionLevel="signature" />
 
+    <!-- @hide @TestApi Allows an application to record sensitive content during media
+         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"/>
+
     <!-- @SystemApi Allows an application to read install sessions
          @hide This is not a third-party API (intended for system apps). -->
     <permission android:name="android.permission.READ_INSTALL_SESSIONS"
diff --git a/core/res/res/values-watch/themes_device_defaults.xml b/core/res/res/values-watch/themes_device_defaults.xml
index 6e804c0..9ad577a 100644
--- a/core/res/res/values-watch/themes_device_defaults.xml
+++ b/core/res/res/values-watch/themes_device_defaults.xml
@@ -181,19 +181,95 @@
 
     <style name="Theme.DeviceDefault.Dialog.Alert" parent="Theme.Material.Dialog.Alert">
         <item name="android:windowFullscreen">true</item>
-        <!-- Color palette Dark -->
+        <!-- Dialog attributes -->
+        <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+        <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+        <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
-        <item name="colorForeground">@color/foreground_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
+        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_dark_device_default</item>
+        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_dark_device_default</item>
+        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_dark_device_default</item>
+        <item name="colorSurface">@color/surface_dark</item>
+        <item name="colorSurfaceHighlight">@color/surface_highlight_dark</item>
+        <item name="colorSurfaceVariant">@color/surface_variant_dark</item>
+        <item name="colorSurfaceHeader">@color/surface_header_dark</item>
+        <item name="colorError">@color/error_color_device_default_dark</item>
         <item name="colorBackground">@color/background_device_default_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
-        <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_device_default</item>
-        <item name="colorButtonNormal">@color/button_normal_device_default_dark</item>
-        <item name="colorError">@color/error_color_device_default_dark</item>
-        <item name="disabledAlpha">@dimen/disabled_alpha_device_default</item>
-        <item name="primaryContentAlpha">@dimen/primary_content_alpha_device_default</item>
-        <item name="secondaryContentAlpha">@dimen/secondary_content_alpha_device_default</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
+        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item>
+        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item>
+        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item>
+        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="colorForeground">@color/foreground_device_default_dark</item>
+        <item name="colorForegroundInverse">@color/foreground_device_default_light</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
+
+        <!-- Progress bar attributes -->
+        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
+
+        <!-- Toolbar attributes -->
+        <item name="toolbarStyle">@style/Widget.DeviceDefault.Toolbar</item>
+
+        <item name="materialColorOnSecondaryFixedVariant">@color/system_on_secondary_fixed_variant</item>
+        <item name="materialColorOnTertiaryFixedVariant">@color/system_on_tertiary_fixed_variant</item>
+        <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_dark</item>
+        <item name="materialColorOnPrimaryFixedVariant">@color/system_on_primary_fixed_variant</item>
+        <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_dark</item>
+        <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_dark</item>
+        <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_dark</item>
+        <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_dark</item>
+        <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
+        <item name="materialColorOnErrorContainer">@color/system_on_error_container_dark</item>
+        <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
+        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
+        <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
+        <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
+        <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
+        <item name="materialColorSecondaryContainer">@color/system_secondary_container_dark</item>
+        <item name="materialColorErrorContainer">@color/system_error_container_dark</item>
+        <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
+        <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
+        <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
+        <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
+        <item name="materialColorSurfaceVariant">@color/system_surface_variant_dark</item>
+        <item name="materialColorTertiaryContainer">@color/system_tertiary_container_dark</item>
+        <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
+        <item name="materialColorPrimaryContainer">@color/system_primary_container_dark</item>
+        <item name="materialColorOnBackground">@color/system_on_background_dark</item>
+        <item name="materialColorPrimaryFixed">@color/system_primary_fixed</item>
+        <item name="materialColorOnSecondary">@color/system_on_secondary_dark</item>
+        <item name="materialColorOnTertiary">@color/system_on_tertiary_dark</item>
+        <item name="materialColorSurfaceDim">@color/system_surface_dim_dark</item>
+        <item name="materialColorSurfaceBright">@color/system_surface_bright_dark</item>
+        <item name="materialColorOnError">@color/system_on_error_dark</item>
+        <item name="materialColorSurface">@color/system_surface_dark</item>
+        <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_dark</item>
+        <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
+        <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
+        <item name="materialColorOutline">@color/system_outline_dark</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
+        <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
+        <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
+        <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
+        <item name="materialColorPrimary">@color/system_primary_dark</item>
+        <item name="materialColorSecondary">@color/system_secondary_dark</item>
+        <item name="materialColorTertiary">@color/system_tertiary_dark</item>
+        <item name="materialColorError">@color/system_error_dark</item>
     </style>
 
     <!-- DeviceDefault theme for a window that should look like the Settings app.  -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index d89f236..5e900f7 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -3287,10 +3287,9 @@
         -->
         <attr name="enableOnBackInvokedCallback" format="boolean"/>
 
-        <!-- Specifies permissions necessary to launch this activity via
-             {@link android.content.Context#startActivity} when passing content URIs. The default
-             value is {@code none}, meaning no specific permissions are required. Setting this
-             attribute restricts activity invocation based on the invoker's permissions. If the
+        <!-- Specifies permissions necessary to launch this activity when passing content URIs. The
+             default value is {@code none}, meaning no specific permissions are required. Setting
+             this attribute restricts activity invocation based on the invoker's permissions. If the
              invoker doesn't have the required permissions, the activity start will be denied via a
              {@link java.lang.SecurityException}.
 
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java
index a034653..10ac05d 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java
@@ -88,13 +88,6 @@
     }
 
     @Test
-    public void setInternalHalCallback_callbackSetInHal() throws Exception {
-        mRadioModule.setInternalHalCallback();
-
-        verify(mBroadcastRadioMock).setTunerCallback(any());
-    }
-
-    @Test
     public void getImage_withValidIdFromRadioModule() {
         int imageId = 1;
 
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
index 262f167..755bcdb 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
@@ -192,66 +192,6 @@
             mHalTunerCallback = (ITunerCallback) invocation.getArguments()[0];
             return null;
         }).when(mBroadcastRadioMock).setTunerCallback(any());
-        mRadioModule.setInternalHalCallback();
-
-        doAnswer(invocation -> {
-            android.hardware.broadcastradio.ProgramSelector halSel =
-                    (android.hardware.broadcastradio.ProgramSelector) invocation.getArguments()[0];
-            mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(halSel, SIGNAL_QUALITY);
-            if (halSel.primaryId.type != IdentifierType.AMFM_FREQUENCY_KHZ) {
-                throw new ServiceSpecificException(Result.NOT_SUPPORTED);
-            }
-            mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
-            return Result.OK;
-        }).when(mBroadcastRadioMock).tune(any());
-
-        doAnswer(invocation -> {
-            if ((boolean) invocation.getArguments()[0]) {
-                mHalCurrentInfo.selector.primaryId.value += AM_FM_FREQUENCY_SPACING;
-            } else {
-                mHalCurrentInfo.selector.primaryId.value -= AM_FM_FREQUENCY_SPACING;
-            }
-            mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
-            mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
-            mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
-            return Result.OK;
-        }).when(mBroadcastRadioMock).step(anyBoolean());
-
-        doAnswer(invocation -> {
-            if (mHalCurrentInfo == null) {
-                android.hardware.broadcastradio.ProgramSelector placeHolderSelector =
-                        AidlTestUtils.makeHalFmSelector(/* freq= */ 97300);
-
-                mHalTunerCallback.onTuneFailed(Result.TIMEOUT, placeHolderSelector);
-                return Result.OK;
-            }
-            mHalCurrentInfo.selector.primaryId.value = getSeekFrequency(
-                    mHalCurrentInfo.selector.primaryId.value,
-                    !(boolean) invocation.getArguments()[0]);
-            mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
-            mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
-            mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
-            return Result.OK;
-        }).when(mBroadcastRadioMock).seek(anyBoolean(), anyBoolean());
-
-        doReturn(null).when(mBroadcastRadioMock).getImage(anyInt());
-
-        doAnswer(invocation -> {
-            int configFlag = (int) invocation.getArguments()[0];
-            if (configFlag == UNSUPPORTED_CONFIG_FLAG) {
-                throw new ServiceSpecificException(Result.NOT_SUPPORTED);
-            }
-            return mHalConfigMap.getOrDefault(configFlag, false);
-        }).when(mBroadcastRadioMock).isConfigFlagSet(anyInt());
-
-        doAnswer(invocation -> {
-            int configFlag = (int) invocation.getArguments()[0];
-            if (configFlag == UNSUPPORTED_CONFIG_FLAG) {
-                throw new ServiceSpecificException(Result.NOT_SUPPORTED);
-            }
-            mHalConfigMap.put(configFlag, (boolean) invocation.getArguments()[1]);
-            return null;
-        }).when(mBroadcastRadioMock).setConfigFlag(anyInt(), anyBoolean());
     }
 
     @After
@@ -330,6 +270,7 @@
 
         expect.withMessage("Close state of broadcast radio service session")
                 .that(mTunerSessions[0].isClosed()).isTrue();
+        verify(mBroadcastRadioMock).unsetTunerCallback();
     }
 
     @Test
@@ -351,6 +292,7 @@
                         .that(mTunerSessions[index].isClosed()).isFalse();
             }
         }
+        verify(mBroadcastRadioMock, never()).unsetTunerCallback();
     }
 
     @Test
@@ -378,6 +320,7 @@
             expect.withMessage("Close state of broadcast radio service session of index %s", index)
                     .that(mTunerSessions[index].isClosed()).isTrue();
         }
+        verify(mBroadcastRadioMock).unsetTunerCallback();
     }
 
     @Test
@@ -1295,6 +1238,71 @@
             mAidlTunerCallbackMocks[index] = mock(android.hardware.radio.ITunerCallback.class);
             mTunerSessions[index] = mRadioModule.openSession(mAidlTunerCallbackMocks[index]);
         }
+        setupMockedHalTunerSession();
+    }
+
+    private void setupMockedHalTunerSession() throws Exception {
+        expect.withMessage("Registered HAL tuner callback").that(mHalTunerCallback)
+                .isNotNull();
+
+        doAnswer(invocation -> {
+            android.hardware.broadcastradio.ProgramSelector halSel =
+                    (android.hardware.broadcastradio.ProgramSelector) invocation.getArguments()[0];
+            mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(halSel, SIGNAL_QUALITY);
+            if (halSel.primaryId.type != IdentifierType.AMFM_FREQUENCY_KHZ) {
+                throw new ServiceSpecificException(Result.NOT_SUPPORTED);
+            }
+            mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
+            return Result.OK;
+        }).when(mBroadcastRadioMock).tune(any());
+
+        doAnswer(invocation -> {
+            if ((boolean) invocation.getArguments()[0]) {
+                mHalCurrentInfo.selector.primaryId.value += AM_FM_FREQUENCY_SPACING;
+            } else {
+                mHalCurrentInfo.selector.primaryId.value -= AM_FM_FREQUENCY_SPACING;
+            }
+            mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+            mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+            mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
+            return Result.OK;
+        }).when(mBroadcastRadioMock).step(anyBoolean());
+
+        doAnswer(invocation -> {
+            if (mHalCurrentInfo == null) {
+                android.hardware.broadcastradio.ProgramSelector placeHolderSelector =
+                        AidlTestUtils.makeHalFmSelector(/* freq= */ 97300);
+
+                mHalTunerCallback.onTuneFailed(Result.TIMEOUT, placeHolderSelector);
+                return Result.OK;
+            }
+            mHalCurrentInfo.selector.primaryId.value = getSeekFrequency(
+                    mHalCurrentInfo.selector.primaryId.value,
+                    !(boolean) invocation.getArguments()[0]);
+            mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+            mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+            mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
+            return Result.OK;
+        }).when(mBroadcastRadioMock).seek(anyBoolean(), anyBoolean());
+
+        doReturn(null).when(mBroadcastRadioMock).getImage(anyInt());
+
+        doAnswer(invocation -> {
+            int configFlag = (int) invocation.getArguments()[0];
+            if (configFlag == UNSUPPORTED_CONFIG_FLAG) {
+                throw new ServiceSpecificException(Result.NOT_SUPPORTED);
+            }
+            return mHalConfigMap.getOrDefault(configFlag, false);
+        }).when(mBroadcastRadioMock).isConfigFlagSet(anyInt());
+
+        doAnswer(invocation -> {
+            int configFlag = (int) invocation.getArguments()[0];
+            if (configFlag == UNSUPPORTED_CONFIG_FLAG) {
+                throw new ServiceSpecificException(Result.NOT_SUPPORTED);
+            }
+            mHalConfigMap.put(configFlag, (boolean) invocation.getArguments()[1]);
+            return null;
+        }).when(mBroadcastRadioMock).setConfigFlag(anyInt(), anyBoolean());
     }
 
     private long getSeekFrequency(long currentFrequency, boolean seekDown) {
diff --git a/core/tests/PlatformCompatFramework/Android.bp b/core/tests/PlatformCompatFramework/Android.bp
index 95e23ad..2621d28 100644
--- a/core/tests/PlatformCompatFramework/Android.bp
+++ b/core/tests/PlatformCompatFramework/Android.bp
@@ -18,6 +18,7 @@
     static_libs: [
         "junit",
         "androidx.test.rules",
+        "flag-junit",
     ],
     platform_apis: true,
 }
diff --git a/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java b/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java
index a052543..12a42f9 100644
--- a/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java
+++ b/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java
@@ -19,9 +19,17 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import com.android.internal.compat.flags.Flags;
+
+import org.junit.Rule;
 import org.junit.Test;
 
 public class ChangeReporterTest {
+
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Test
     public void testStatsLogOnce() {
         ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
@@ -63,7 +71,7 @@
         ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
         int myUid = 1022, otherUid = 1023;
         long myChangeId = 500L, otherChangeId = 600L;
-        int myState = ChangeReporter.STATE_ENABLED, otherState = ChangeReporter.STATE_DISABLED;
+        int myState = ChangeReporter.STATE_ENABLED, otherState = ChangeReporter.STATE_LOGGED;
 
         assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myState));
         reporter.reportChange(myUid, myChangeId, myState);
@@ -112,4 +120,80 @@
         reporter.stopDebugLogAll();
         assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myState));
     }
+
+    @Test
+    public void testDebugLogWithFlagOnAndOldSdk() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_SKIP_OLD_AND_DISABLED_COMPAT_LOGGING);
+        ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
+        int myUid = 1022;
+        long myChangeId = 500L;
+        int myEnabledState = ChangeReporter.STATE_ENABLED;
+        int myDisabledState = ChangeReporter.STATE_DISABLED;
+
+        // Report will not log if target sdk is before the previous version.
+        assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, false));
+
+        reporter.resetReportedChanges(myUid);
+
+        // Report will be logged if target sdk is the latest version.
+        assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, true));
+
+        reporter.resetReportedChanges(myUid);
+
+        // If the report is disabled, the sdk version shouldn't matter.
+        assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myDisabledState, true));
+    }
+
+    @Test
+    public void testDebugLogWithFlagOnAndDisabledChange() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_SKIP_OLD_AND_DISABLED_COMPAT_LOGGING);
+        ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
+        int myUid = 1022;
+        long myChangeId = 500L;
+        int myEnabledState = ChangeReporter.STATE_ENABLED;
+        int myDisabledState = ChangeReporter.STATE_DISABLED;
+
+        // Report will not log if the change is disabled.
+        assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myDisabledState, true));
+
+        reporter.resetReportedChanges(myUid);
+
+        // Report will be logged if the change is enabled.
+        assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, true));
+
+        reporter.resetReportedChanges(myUid);
+
+        // If the report is not the latest version, the disabled state doesn't matter.
+        assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, false));
+    }
+
+    @Test
+    public void testDebugLogWithFlagOff() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_SKIP_OLD_AND_DISABLED_COMPAT_LOGGING);
+        ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
+        int myUid = 1022;
+        long myChangeId = 500L;
+        int myEnabledState = ChangeReporter.STATE_ENABLED;
+        int myDisabledState = ChangeReporter.STATE_DISABLED;
+
+        // Report will be logged even if the change is not the latest sdk but the flag is off.
+        assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, false));
+
+        reporter.resetReportedChanges(myUid);
+
+        // Report will be logged if the change is enabled and the latest sdk but the flag is off.
+        assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, true));
+
+        reporter.resetReportedChanges(myUid);
+
+        // Report will be logged if the change is disabled and the latest sdk but the flag is
+        // off.
+        assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myDisabledState, true));
+
+        reporter.resetReportedChanges(myUid);
+
+        // Report will be logged if the change is disabled and not the latest sdk but the flag is
+        // off.
+        assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myDisabledState, false));
+    }
 }
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/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index b60b806f..a5c9624 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -23,6 +23,8 @@
 import static android.view.inputmethod.Flags.initiationWithoutInputConnection;
 import static android.view.stylus.HandwritingTestUtil.createView;
 
+import static com.android.text.flags.Flags.handwritingCursorPosition;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assume.assumeFalse;
@@ -129,6 +131,7 @@
     public void onTouchEvent_startHandwriting_when_stylusMoveOnce_withinHWArea() {
         mTestView1.setText("hello");
         when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4);
+        when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(0);
 
         mHandwritingInitiator.onInputConnectionCreated(mTestView1);
         final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
@@ -148,9 +151,51 @@
         // After IMM.startHandwriting is triggered, onTouchEvent should return true for ACTION_MOVE
         // events so that the events are not dispatched to the view tree.
         assertThat(onTouchEventResult2).isTrue();
-        // Since the stylus down point was inside the TextView's bounds, the handwriting initiator
-        // does not need to set the cursor position.
-        verify(mTestView1, never()).setSelection(anyInt());
+        if (handwritingCursorPosition()) {
+            // Cursor is placed at the end of the text.
+            verify(mTestView1).setSelection(5);
+        } else {
+            // Since the stylus down point was inside the TextView's bounds, the handwriting
+            // initiator does not need to set the cursor position.
+            verify(mTestView1, never()).setSelection(anyInt());
+        }
+    }
+
+    @Test
+    public void onTouchEvent_startHandwriting_multipleParagraphs() {
+        // End of line 0 is offset 10, end of line 1 is offset 20, end of line 2 is offset 30, end
+        // of line 3 is offset 40.
+        mTestView1.setText("line 0    \nline 1   \nline 2   \nline 3   ");
+        mTestView1.layout(0, 0, 500, 500);
+        when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4);
+        when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(2);
+
+        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
+        final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2;
+        MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
+        boolean onTouchEventResult1 = mHandwritingInitiator.onTouchEvent(stylusEvent1);
+
+        final int x2 = x1 + mHandwritingSlop * 2;
+        final int y2 = y1;
+
+        MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
+        boolean onTouchEventResult2 = mHandwritingInitiator.onTouchEvent(stylusEvent2);
+
+        // Stylus movement within HandwritingArea should trigger IMM.startHandwriting once.
+        verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1);
+        assertThat(onTouchEventResult1).isFalse();
+        // After IMM.startHandwriting is triggered, onTouchEvent should return true for ACTION_MOVE
+        // events so that the events are not dispatched to the view tree.
+        assertThat(onTouchEventResult2).isTrue();
+        if (handwritingCursorPosition()) {
+            // Cursor is placed at the end of the paragraph containing line 2.
+            verify(mTestView1).setSelection(30);
+        } else {
+            // Since the stylus down point was inside the TextView's bounds, the handwriting
+            // initiator does not need to set the cursor position.
+            verify(mTestView1, never()).setSelection(anyInt());
+        }
     }
 
     @Test
@@ -197,6 +242,7 @@
     public void onTouchEvent_startHandwriting_when_stylusMove_withinExtendedHWArea() {
         mTestView1.setText("hello");
         when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4);
+        when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(0);
 
         if (!mInitiateWithoutConnection) {
             mHandwritingInitiator.onInputConnectionCreated(mTestView1);
@@ -214,9 +260,14 @@
 
         // Stylus movement within extended HandwritingArea should trigger IMM.startHandwriting once.
         verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1);
-        // Since the stylus down point was outside the TextView's bounds, the handwriting initiator
-        // sets the cursor position.
-        verify(mTestView1).setSelection(4);
+        if (handwritingCursorPosition()) {
+            // Cursor is placed at the end of the text.
+            verify(mTestView1).setSelection(5);
+        } else {
+            // Since the stylus down point was outside the TextView's bounds, the handwriting
+            // initiator sets the cursor position.
+            verify(mTestView1).setSelection(4);
+        }
     }
 
     @Test
@@ -246,6 +297,8 @@
     public void onTouchEvent_startHandwriting_servedViewUpdate_stylusMoveInExtendedHWArea() {
         mTestView1.setText("hello");
         when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4);
+        when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(0);
+
         // The stylus down point is between mTestView1 and  mTestView2, but it is within the
         // extended handwriting area of both views. It is closer to mTestView1.
         final int x1 = sHwArea1.right + HW_BOUNDS_OFFSETS_RIGHT_PX / 2;
@@ -278,9 +331,14 @@
         // Handwriting is started for this view since  the stylus down point is closest to this
         // view.
         verify(mHandwritingInitiator).startHandwriting(mTestView1);
-        // Since the stylus down point was outside the TextView's bounds, the handwriting initiator
-        // sets the cursor position.
-        verify(mTestView1).setSelection(4);
+        if (handwritingCursorPosition()) {
+            // Cursor is placed at the end of the text.
+            verify(mTestView1).setSelection(5);
+        } else {
+            // Since the stylus down point was outside the TextView's bounds, the handwriting
+            //  initiator sets the cursor position.
+            verify(mTestView1).setSelection(4);
+        }
     }
 
 
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java
index 76772b7..0897726 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java
@@ -16,6 +16,12 @@
 
 package android.hardware.devicestate;
 
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNotNull;
 
@@ -33,6 +39,7 @@
 import org.junit.runners.JUnit4;
 
 import java.util.List;
+import java.util.Set;
 
 /**
  * Unit tests for {@link DeviceStateInfo}.
@@ -44,11 +51,25 @@
 public final class DeviceStateInfoTest {
 
     private static final DeviceState DEVICE_STATE_0 = new DeviceState(
-            new DeviceState.Configuration.Builder(0, "STATE_0").build());
+            new DeviceState.Configuration.Builder(0, "STATE_0")
+                    .setSystemProperties(
+                            Set.of(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS,
+                                    PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY))
+                    .setPhysicalProperties(
+                            Set.of(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED))
+                    .build());
     private static final DeviceState DEVICE_STATE_1 = new DeviceState(
-            new DeviceState.Configuration.Builder(1, "STATE_1").build());
+            new DeviceState.Configuration.Builder(1, "STATE_1")
+                    .setSystemProperties(
+                            Set.of(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY))
+                    .setPhysicalProperties(
+                            Set.of(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN))
+                    .build());
     private static final DeviceState DEVICE_STATE_2 = new DeviceState(
-            new DeviceState.Configuration.Builder(2, "STATE_2").build());
+            new DeviceState.Configuration.Builder(2, "STATE_2")
+                    .setSystemProperties(
+                            Set.of(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY))
+                    .build());
 
     @Test
     public void create() {
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java
index 68de21f..78d4324 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java
@@ -93,4 +93,22 @@
 
         Assert.assertEquals(originalState, new DeviceState(stateConfiguration));
     }
+
+    @Test
+    public void writeToParcel_noPhysicalProperties() {
+        final DeviceState originalState = new DeviceState(
+                new DeviceState.Configuration.Builder(0, "TEST_STATE")
+                        .setSystemProperties(Set.of(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS,
+                                PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST))
+                        .build());
+
+        final Parcel parcel = Parcel.obtain();
+        originalState.getConfiguration().writeToParcel(parcel, 0 /* flags */);
+        parcel.setDataPosition(0);
+
+        final DeviceState.Configuration stateConfiguration =
+                DeviceState.Configuration.CREATOR.createFromParcel(parcel);
+
+        Assert.assertEquals(originalState, new DeviceState(stateConfiguration));
+    }
 }
diff --git a/data/etc/OWNERS b/data/etc/OWNERS
index 245f216..701d145 100644
--- a/data/etc/OWNERS
+++ b/data/etc/OWNERS
@@ -12,3 +12,4 @@
 
 per-file preinstalled-packages* = file:/MULTIUSER_OWNERS
 per-file services.core.protolog.json =  file:/services/core/java/com/android/server/wm/OWNERS
+per-file core.protolog.pb = file:/services/core/java/com/android/server/wm/OWNERS
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/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index 539832e..d44033c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -523,8 +523,8 @@
     /**
      * Whether we should use jump cut for the change transition.
      * This normally happens when opening a new secondary with the existing primary using a
-     * different split layout. This can be complicated, like from horizontal to vertical split with
-     * new split pairs.
+     * different split layout (ratio or direction). This can be complicated, like from horizontal to
+     * vertical split with new split pairs.
      * Uses a jump cut animation to simplify.
      */
     private boolean shouldUseJumpCutForChangeTransition(@NonNull TransitionInfo info) {
@@ -553,8 +553,8 @@
         }
 
         // Check if the transition contains both opening and closing windows.
-        boolean hasOpeningWindow = false;
-        boolean hasClosingWindow = false;
+        final List<TransitionInfo.Change> openChanges = new ArrayList<>();
+        final List<TransitionInfo.Change> closeChanges = new ArrayList<>();
         for (TransitionInfo.Change change : info.getChanges()) {
             if (changingChanges.contains(change)) {
                 continue;
@@ -564,10 +564,30 @@
                 // No-op if it will be covered by the changing parent window.
                 continue;
             }
-            hasOpeningWindow |= TransitionUtil.isOpeningType(change.getMode());
-            hasClosingWindow |= TransitionUtil.isClosingType(change.getMode());
+            if (TransitionUtil.isOpeningType(change.getMode())) {
+                openChanges.add(change);
+            } else if (TransitionUtil.isClosingType(change.getMode())) {
+                closeChanges.add(change);
+            }
         }
-        return hasOpeningWindow && hasClosingWindow;
+        if (openChanges.isEmpty() || closeChanges.isEmpty()) {
+            // Only skip if the transition contains both open and close.
+            return false;
+        }
+        if (changingChanges.size() != 1 || openChanges.size() != 1 || closeChanges.size() != 1) {
+            // Skip when there are too many windows involved.
+            return true;
+        }
+        final TransitionInfo.Change changingChange = changingChanges.get(0);
+        final TransitionInfo.Change openChange = openChanges.get(0);
+        final TransitionInfo.Change closeChange = closeChanges.get(0);
+        if (changingChange.getStartAbsBounds().equals(openChange.getEndAbsBounds())
+                && changingChange.getEndAbsBounds().equals(closeChange.getStartAbsBounds())) {
+            // Don't skip if the transition is a simple shifting without split direction or ratio
+            // change. For example, A|B -> B|C.
+            return false;
+        }
+        return true;
     }
 
     /** Updates the changes to end states in {@code startTransaction} for jump cut animation. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 9585842..4455a3c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1259,12 +1259,14 @@
      * Expands and selects a bubble based on the provided {@link BubbleEntry}. If no bubble
      * exists for this entry, and it is able to bubble, a new bubble will be created.
      *
-     * This is the method to use when opening a bubble via a notification or in a state where
+     * <p>This is the method to use when opening a bubble via a notification or in a state where
      * the device might not be unlocked.
      *
      * @param entry the entry to use for the bubble.
      */
     public void expandStackAndSelectBubble(BubbleEntry entry) {
+        ProtoLog.d(WM_SHELL_BUBBLES, "opening bubble from notification key=%s mIsStatusBarShade=%b",
+                entry.getKey(), mIsStatusBarShade);
         if (mIsStatusBarShade) {
             mNotifEntryToExpandOnShadeUnlock = null;
 
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/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 8b2ec0a..8d489e1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -846,8 +846,10 @@
     static ShellController provideShellController(Context context,
             ShellInit shellInit,
             ShellCommandHandler shellCommandHandler,
+            DisplayInsetsController displayInsetsController,
             @ShellMainThread ShellExecutor mainExecutor) {
-        return new ShellController(context, shellInit, shellCommandHandler, mainExecutor);
+        return new ShellController(context, shellInit, shellCommandHandler,
+                displayInsetsController, mainExecutor);
     }
 
     //
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index fb3c35b..04f0f44 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -57,6 +57,8 @@
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.dagger.back.ShellBackAnimationModule;
 import com.android.wm.shell.dagger.pip.PipModule;
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
+import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
 import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -509,6 +511,7 @@
             ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
             DragToDesktopTransitionHandler dragToDesktopTransitionHandler,
             @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
+            DesktopModeLoggerTransitionObserver desktopModeLoggerTransitionObserver,
             LaunchAdjacentController launchAdjacentController,
             RecentsTransitionHandler recentsTransitionHandler,
             MultiInstanceHelper multiInstanceHelper,
@@ -518,7 +521,8 @@
                 displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
                 dragAndDropController, transitions, enterDesktopTransitionHandler,
                 exitDesktopTransitionHandler, toggleResizeDesktopTaskTransitionHandler,
-                dragToDesktopTransitionHandler, desktopModeTaskRepository, launchAdjacentController,
+                dragToDesktopTransitionHandler, desktopModeTaskRepository,
+                desktopModeLoggerTransitionObserver, launchAdjacentController,
                 recentsTransitionHandler, multiInstanceHelper, mainExecutor);
     }
 
@@ -562,6 +566,22 @@
         return new DesktopModeTaskRepository();
     }
 
+    @WMSingleton
+    @Provides
+    static DesktopModeLoggerTransitionObserver provideDesktopModeLoggerTransitionObserver(
+            ShellInit shellInit,
+            Transitions transitions,
+            DesktopModeEventLogger desktopModeEventLogger) {
+        return new DesktopModeLoggerTransitionObserver(
+                shellInit, transitions, desktopModeEventLogger);
+    }
+
+    @WMSingleton
+    @Provides
+    static DesktopModeEventLogger provideDesktopModeEventLogger() {
+        return new DesktopModeEventLogger();
+    }
+
     //
     // Drag and drop
     //
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
new file mode 100644
index 0000000..a10c7c0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.ActivityTaskManager.INVALID_TASK_ID
+import android.app.TaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.os.IBinder
+import android.util.SparseArray
+import android.view.SurfaceControl
+import android.view.WindowManager
+import android.window.TransitionInfo
+import androidx.annotation.VisibleForTesting
+import androidx.core.util.containsKey
+import androidx.core.util.forEach
+import androidx.core.util.isEmpty
+import androidx.core.util.isNotEmpty
+import androidx.core.util.plus
+import androidx.core.util.putAll
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.InstanceIdSequence
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.shared.TransitionUtil
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.util.KtProtoLog
+
+/**
+ * A [Transitions.TransitionObserver] that observes transitions and the proposed changes to log
+ * appropriate desktop mode session log events. This observes transitions related to desktop mode
+ * and other transitions that originate both within and outside shell.
+ */
+class DesktopModeLoggerTransitionObserver(
+    shellInit: ShellInit,
+    private val transitions: Transitions,
+    private val desktopModeEventLogger: DesktopModeEventLogger
+) : Transitions.TransitionObserver {
+
+    private val idSequence: InstanceIdSequence by lazy { InstanceIdSequence(Int.MAX_VALUE) }
+
+    init {
+        if (Transitions.ENABLE_SHELL_TRANSITIONS && DesktopModeStatus.isEnabled()) {
+            shellInit.addInitCallback(this::onInit, this)
+        }
+    }
+
+    // A sparse array of visible freeform tasks and taskInfos
+    private val visibleFreeformTaskInfos: SparseArray<TaskInfo> = SparseArray()
+
+    // Caching the taskInfos to handle canceled recents animations, if we identify that the recents
+    // animation was cancelled, we restore these tasks to calculate the post-Transition state
+    private val tasksSavedForRecents: SparseArray<TaskInfo> = SparseArray()
+
+    // The instanceId for the current logging session
+    private var loggerInstanceId: InstanceId? = null
+
+    private val isSessionActive: Boolean
+        get() = loggerInstanceId != null
+
+    private fun setSessionInactive() {
+        loggerInstanceId = null
+    }
+
+    fun onInit() {
+        transitions.registerObserver(this)
+    }
+
+    override fun onTransitionReady(
+        transition: IBinder,
+        info: TransitionInfo,
+        startTransaction: SurfaceControl.Transaction,
+        finishTransaction: SurfaceControl.Transaction
+    ) {
+        // this was a new recents animation
+        if (info.isRecentsTransition() && tasksSavedForRecents.isEmpty()) {
+            KtProtoLog.v(
+                WM_SHELL_DESKTOP_MODE,
+                "DesktopModeLogger: Recents animation running, saving tasks for later"
+            )
+            // TODO (b/326391303) - avoid logging session exit if we can identify a cancelled
+            // recents animation
+
+            // when recents animation is running, all freeform tasks are sent TO_BACK temporarily
+            // if the user ends up at home, we need to update the visible freeform tasks
+            // if the user cancels the animation, the subsequent transition is NONE
+            // if the user opens a new task, the subsequent transition is OPEN with flag
+            tasksSavedForRecents.putAll(visibleFreeformTaskInfos)
+        }
+
+        // figure out what the new state of freeform tasks would be post transition
+        var postTransitionVisibleFreeformTasks = getPostTransitionVisibleFreeformTaskInfos(info)
+
+        // A canceled recents animation is followed by a TRANSIT_NONE transition with no flags, if
+        // that's the case, we might have accidentally logged a session exit and would need to
+        // revaluate again. Add all the tasks back.
+        // This will start a new desktop mode session.
+        if (
+            info.type == WindowManager.TRANSIT_NONE &&
+                info.flags == 0 &&
+                tasksSavedForRecents.isNotEmpty()
+        ) {
+            KtProtoLog.v(
+                WM_SHELL_DESKTOP_MODE,
+                "DesktopModeLogger: Canceled recents animation, restoring tasks"
+            )
+            // restore saved tasks in the updated set and clear for next use
+            postTransitionVisibleFreeformTasks += tasksSavedForRecents
+            tasksSavedForRecents.clear()
+        }
+
+        // identify if we need to log any changes and update the state of visible freeform tasks
+        identifyLogEventAndUpdateState(
+            transitionInfo = info,
+            preTransitionVisibleFreeformTasks = visibleFreeformTaskInfos,
+            postTransitionVisibleFreeformTasks = postTransitionVisibleFreeformTasks
+        )
+    }
+
+    override fun onTransitionStarting(transition: IBinder) {}
+
+    override fun onTransitionMerged(merged: IBinder, playing: IBinder) {}
+
+    override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {}
+
+    private fun getPostTransitionVisibleFreeformTaskInfos(
+        info: TransitionInfo
+    ): SparseArray<TaskInfo> {
+        // device is sleeping, so no task will be visible anymore
+        if (info.type == WindowManager.TRANSIT_SLEEP) {
+            return SparseArray()
+        }
+
+        // filter changes involving freeform tasks or tasks that were cached in previous state
+        val changesToFreeformWindows =
+            info.changes
+                .filter { it.taskInfo != null && it.requireTaskInfo().taskId != INVALID_TASK_ID }
+                .filter {
+                    it.requireTaskInfo().isFreeformWindow() ||
+                        visibleFreeformTaskInfos.containsKey(it.requireTaskInfo().taskId)
+                }
+
+        val postTransitionFreeformTasks: SparseArray<TaskInfo> = SparseArray()
+        // start off by adding all existing tasks
+        postTransitionFreeformTasks.putAll(visibleFreeformTaskInfos)
+
+        // the combined set of taskInfos we are interested in this transition change
+        for (change in changesToFreeformWindows) {
+            val taskInfo = change.requireTaskInfo()
+
+            // check if this task existed as freeform window in previous cached state and it's now
+            // changing window modes
+            if (
+                visibleFreeformTaskInfos.containsKey(taskInfo.taskId) &&
+                    visibleFreeformTaskInfos.get(taskInfo.taskId).isFreeformWindow() &&
+                    !taskInfo.isFreeformWindow()
+            ) {
+                postTransitionFreeformTasks.remove(taskInfo.taskId)
+                // no need to evaluate new visibility of this task, since it's no longer a freeform
+                // window
+                continue
+            }
+
+            // check if the task is visible after this change, otherwise remove it
+            if (isTaskVisibleAfterChange(change)) {
+                postTransitionFreeformTasks.put(taskInfo.taskId, taskInfo)
+            } else {
+                postTransitionFreeformTasks.remove(taskInfo.taskId)
+            }
+        }
+
+        KtProtoLog.v(
+            WM_SHELL_DESKTOP_MODE,
+            "DesktopModeLogger: taskInfo map after processing changes %s",
+            postTransitionFreeformTasks.size()
+        )
+
+        return postTransitionFreeformTasks
+    }
+
+    /**
+     * Look at the [TransitionInfo.Change] and figure out if this task will be visible after this
+     * change is processed
+     */
+    private fun isTaskVisibleAfterChange(change: TransitionInfo.Change): Boolean =
+        when {
+            TransitionUtil.isOpeningType(change.mode) -> true
+            TransitionUtil.isClosingType(change.mode) -> false
+            // change mode TRANSIT_CHANGE is only for visible to visible transitions
+            change.mode == WindowManager.TRANSIT_CHANGE -> true
+            else -> false
+        }
+
+    /**
+     * Log the appropriate log event based on the new state of TasksInfos and previously cached
+     * state and update it
+     */
+    private fun identifyLogEventAndUpdateState(
+        transitionInfo: TransitionInfo,
+        preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
+        postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>
+    ) {
+        if (
+            postTransitionVisibleFreeformTasks.isEmpty() &&
+                preTransitionVisibleFreeformTasks.isNotEmpty() &&
+                isSessionActive
+        ) {
+            // Sessions is finishing, log task updates followed by an exit event
+            identifyAndLogTaskUpdates(
+                loggerInstanceId!!.id,
+                preTransitionVisibleFreeformTasks,
+                postTransitionVisibleFreeformTasks
+            )
+
+            desktopModeEventLogger.logSessionExit(
+                loggerInstanceId!!.id,
+                getExitReason(transitionInfo)
+            )
+
+            setSessionInactive()
+        } else if (
+            postTransitionVisibleFreeformTasks.isNotEmpty() &&
+                preTransitionVisibleFreeformTasks.isEmpty() &&
+                !isSessionActive
+        ) {
+            // Session is starting, log enter event followed by task updates
+            loggerInstanceId = idSequence.newInstanceId()
+            desktopModeEventLogger.logSessionEnter(
+                loggerInstanceId!!.id,
+                getEnterReason(transitionInfo)
+            )
+
+            identifyAndLogTaskUpdates(
+                loggerInstanceId!!.id,
+                preTransitionVisibleFreeformTasks,
+                postTransitionVisibleFreeformTasks
+            )
+        } else if (isSessionActive) {
+            // Session is neither starting, nor finishing, log task updates if there are any
+            identifyAndLogTaskUpdates(
+                loggerInstanceId!!.id,
+                preTransitionVisibleFreeformTasks,
+                postTransitionVisibleFreeformTasks
+            )
+        }
+
+        // update the state to the new version
+        visibleFreeformTaskInfos.clear()
+        visibleFreeformTaskInfos.putAll(postTransitionVisibleFreeformTasks)
+    }
+
+    // TODO(b/326231724) - Add logging around taskInfoChanges Updates
+    /** Compare the old and new state of taskInfos and identify and log the changes */
+    private fun identifyAndLogTaskUpdates(
+        sessionId: Int,
+        preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
+        postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>
+    ) {
+        // find new tasks that were added
+        postTransitionVisibleFreeformTasks.forEach { taskId, taskInfo ->
+            if (!preTransitionVisibleFreeformTasks.containsKey(taskId)) {
+                desktopModeEventLogger.logTaskAdded(sessionId, buildTaskUpdateForTask(taskInfo))
+            }
+        }
+
+        // find old tasks that were removed
+        preTransitionVisibleFreeformTasks.forEach { taskId, taskInfo ->
+            if (!postTransitionVisibleFreeformTasks.containsKey(taskId)) {
+                desktopModeEventLogger.logTaskRemoved(sessionId, buildTaskUpdateForTask(taskInfo))
+            }
+        }
+    }
+
+    // TODO(b/326231724: figure out how to get taskWidth and taskHeight from TaskInfo
+    private fun buildTaskUpdateForTask(taskInfo: TaskInfo): TaskUpdate {
+        val taskUpdate = TaskUpdate(taskInfo.taskId, taskInfo.userId)
+        // add task x, y if available
+        taskInfo.positionInParent?.let { taskUpdate.copy(taskX = it.x, taskY = it.y) }
+
+        return taskUpdate
+    }
+
+    /** Get [EnterReason] for this session enter */
+    private fun getEnterReason(transitionInfo: TransitionInfo): EnterReason {
+        // TODO(b/326231756) - Add support for missing enter reasons
+        return when (transitionInfo.type) {
+            WindowManager.TRANSIT_WAKE -> EnterReason.SCREEN_ON
+            Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP -> EnterReason.APP_HANDLE_DRAG
+            Transitions.TRANSIT_MOVE_TO_DESKTOP -> EnterReason.APP_HANDLE_MENU_BUTTON
+            WindowManager.TRANSIT_OPEN -> EnterReason.APP_FREEFORM_INTENT
+            else -> EnterReason.UNKNOWN_ENTER
+        }
+    }
+
+    /** Get [ExitReason] for this session exit */
+    private fun getExitReason(transitionInfo: TransitionInfo): ExitReason {
+        // TODO(b/326231756) - Add support for missing exit reasons
+        return when {
+            transitionInfo.type == WindowManager.TRANSIT_SLEEP -> ExitReason.SCREEN_OFF
+            transitionInfo.type == WindowManager.TRANSIT_CLOSE -> ExitReason.TASK_FINISHED
+            transitionInfo.type == Transitions.TRANSIT_EXIT_DESKTOP_MODE -> ExitReason.DRAG_TO_EXIT
+            transitionInfo.isRecentsTransition() -> ExitReason.RETURN_HOME_OR_OVERVIEW
+            else -> ExitReason.UNKNOWN_EXIT
+        }
+    }
+
+    /** Adds tasks to the saved copy of freeform taskId, taskInfo. Only used for testing. */
+    @VisibleForTesting
+    fun addTaskInfosToCachedMap(taskInfo: TaskInfo) {
+        visibleFreeformTaskInfos.set(taskInfo.taskId, taskInfo)
+    }
+
+    @VisibleForTesting fun getLoggerSessionId(): Int? = loggerInstanceId?.id
+
+    @VisibleForTesting
+    fun setLoggerSessionId(id: Int) {
+        loggerInstanceId = InstanceId.fakeInstanceId(id)
+    }
+
+    private fun TransitionInfo.Change.requireTaskInfo(): RunningTaskInfo {
+        return this.taskInfo ?: throw IllegalStateException("Expected TaskInfo in the Change")
+    }
+
+    private fun TaskInfo.isFreeformWindow(): Boolean {
+        return this.windowingMode == WINDOWING_MODE_FREEFORM
+    }
+
+    private fun TransitionInfo.isRecentsTransition(): Boolean {
+        return this.type == WindowManager.TRANSIT_TO_FRONT &&
+            this.flags == WindowManager.TRANSIT_FLAG_IS_RECENTS
+    }
+}
\ No newline at end of file
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 95237c3..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
@@ -102,6 +103,7 @@
         ToggleResizeDesktopTaskTransitionHandler,
         private val dragToDesktopTransitionHandler: DragToDesktopTransitionHandler,
         private val desktopModeTaskRepository: DesktopModeTaskRepository,
+        private val desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver,
         private val launchAdjacentController: LaunchAdjacentController,
         private val recentsTransitionHandler: RecentsTransitionHandler,
         private val multiInstanceHelper: MultiInstanceHelper,
@@ -959,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.
@@ -970,7 +973,8 @@
         taskInfo: RunningTaskInfo,
         position: Point,
         inputCoordinate: PointF,
-        taskBounds: Rect
+        taskBounds: Rect,
+        validDragArea: Rect
     ) {
         if (taskInfo.configuration.windowConfiguration.windowingMode != WINDOWING_MODE_FREEFORM) {
             return
@@ -993,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/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 952e2d4..86c8f04 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
@@ -436,7 +436,11 @@
     }
 
     public void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
-        mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason);
+        if (ENABLE_SHELL_TRANSITIONS) {
+            mStageCoordinator.dismissSplitScreen(toTopTaskId, exitReason);
+        } else {
+            mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason);
+        }
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
index 7f16c5e..af11ebc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
@@ -17,6 +17,7 @@
 package com.android.wm.shell.splitscreen;
 
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_UNKNOWN;
 
 import com.android.wm.shell.sysui.ShellCommandHandler;
 
@@ -45,6 +46,8 @@
                 return runSetSideStagePosition(args, pw);
             case "switchSplitPosition":
                 return runSwitchSplitPosition();
+            case "exitSplitScreen":
+                return runExitSplitScreen(args, pw);
             default:
                 pw.println("Invalid command: " + args[0]);
                 return false;
@@ -91,6 +94,17 @@
         return true;
     }
 
+    private boolean runExitSplitScreen(String[] args, PrintWriter pw) {
+        if (args.length < 2) {
+            // First argument is the action name.
+            pw.println("Error: task id should be provided as arguments");
+            return false;
+        }
+        final int taskId = Integer.parseInt(args[1]);
+        mController.exitSplitScreen(taskId, EXIT_REASON_UNKNOWN);
+        return true;
+    }
+
     @Override
     public void printShellCommandHelp(PrintWriter pw, String prefix) {
         pw.println(prefix + "moveToSideStage <taskId> <SideStagePosition>");
@@ -101,5 +115,7 @@
         pw.println(prefix + "  Sets the position of the side-stage.");
         pw.println(prefix + "switchSplitPosition");
         pw.println(prefix + "  Reverses the split.");
+        pw.println(prefix + "exitSplitScreen <taskId>");
+        pw.println(prefix + "  Exits split screen and leaves the provided split task on top.");
     }
 }
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 9dd4c19..36368df9 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
@@ -29,6 +29,7 @@
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -1450,6 +1451,7 @@
         mExitSplitScreenOnHide = exitSplitScreenOnHide;
     }
 
+    /** Exits split screen with legacy transition */
     void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
         ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitSplitScreen: topTaskId=%d reason=%s active=%b",
                 toTopTaskId, exitReasonToString(exitReason), mMainStage.isActive());
@@ -1469,6 +1471,7 @@
         applyExitSplitScreen(childrenToTop, wct, exitReason);
     }
 
+    /** Exits split screen with legacy transition */
     private void exitSplitScreen(@Nullable StageTaskListener childrenToTop,
             @ExitReason int exitReason) {
         ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitSplitScreen: mainStageToTop=%b reason=%s active=%b",
@@ -1546,6 +1549,14 @@
         }
     }
 
+    void dismissSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
+        if (!mMainStage.isActive()) return;
+        final int stage = getStageOfTask(toTopTaskId);
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        prepareExitSplitScreen(stage, wct);
+        mSplitTransitions.startDismissTransition(wct, this, stage, exitReason);
+    }
+
     /**
      * Overridden by child classes.
      */
@@ -1611,6 +1622,8 @@
                 // User has used a keyboard shortcut to go back to fullscreen from split
             case EXIT_REASON_DESKTOP_MODE:
                 // One of the children enters desktop mode
+            case EXIT_REASON_UNKNOWN:
+                // Unknown reason
                 return true;
             default:
                 return false;
@@ -2776,7 +2789,7 @@
                                 + " with " + taskInfo.taskId + " before startAnimation().");
                         record.addRecord(stage, true, taskInfo.taskId);
                     }
-                } else if (isClosingType(change.getMode())) {
+                } else if (change.getMode() == TRANSIT_CLOSE) {
                     if (stage.containsTask(taskInfo.taskId)) {
                         record.addRecord(stage, false, taskInfo.taskId);
                         Log.w(TAG, "Expected onTaskVanished on " + stage + " to have been called"
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/sysui/DisplayImeChangeListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/DisplayImeChangeListener.java
new file mode 100644
index 0000000..a94f802
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/DisplayImeChangeListener.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.sysui;
+
+import android.graphics.Rect;
+
+/**
+ * Callbacks for when the Display IME changes.
+ */
+public interface DisplayImeChangeListener {
+    /**
+     * Called when the ime bounds change.
+     */
+    default void onImeBoundsChanged(int displayId, Rect bounds) {}
+
+    /**
+     * Called when the IME visibility change.
+     */
+    default void onImeVisibilityChanged(int displayId, boolean isShowing) {}
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
index a7843e2..2f6edc2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
@@ -30,21 +30,28 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
+import android.graphics.Rect;
 import android.os.Bundle;
 import android.util.ArrayMap;
+import android.view.InsetsSource;
+import android.view.InsetsState;
 import android.view.SurfaceControlRegistry;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
 import com.android.wm.shell.common.ExternalInterfaceBinder;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.annotations.ExternalThread;
 
 import java.io.PrintWriter;
 import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
 import java.util.function.Supplier;
 
 /**
@@ -57,6 +64,7 @@
     private final ShellInit mShellInit;
     private final ShellCommandHandler mShellCommandHandler;
     private final ShellExecutor mMainExecutor;
+    private final DisplayInsetsController mDisplayInsetsController;
     private final ShellInterfaceImpl mImpl = new ShellInterfaceImpl();
 
     private final CopyOnWriteArrayList<ConfigurationChangeListener> mConfigChangeListeners =
@@ -65,6 +73,8 @@
             new CopyOnWriteArrayList<>();
     private final CopyOnWriteArrayList<UserChangeListener> mUserChangeListeners =
             new CopyOnWriteArrayList<>();
+    private final ConcurrentHashMap<DisplayImeChangeListener, Executor> mDisplayImeChangeListeners =
+            new ConcurrentHashMap<>();
 
     private ArrayMap<String, Supplier<ExternalInterfaceBinder>> mExternalInterfaceSuppliers =
             new ArrayMap<>();
@@ -73,20 +83,53 @@
 
     private Configuration mLastConfiguration;
 
+    private OnInsetsChangedListener mInsetsChangeListener = new OnInsetsChangedListener() {
+        private InsetsState mInsetsState = new InsetsState();
+
+        @Override
+        public void insetsChanged(InsetsState insetsState) {
+            if (mInsetsState == insetsState) {
+                return;
+            }
+
+            InsetsSource oldSource = mInsetsState.peekSource(InsetsSource.ID_IME);
+            boolean wasVisible = (oldSource != null && oldSource.isVisible());
+            Rect oldFrame = wasVisible ? oldSource.getFrame() : null;
+
+            InsetsSource newSource = insetsState.peekSource(InsetsSource.ID_IME);
+            boolean isVisible = (newSource != null && newSource.isVisible());
+            Rect newFrame = isVisible ? newSource.getFrame() : null;
+
+            if (wasVisible != isVisible) {
+                onImeVisibilityChanged(isVisible);
+            }
+
+            if (newFrame != null && !newFrame.equals(oldFrame)) {
+                onImeBoundsChanged(newFrame);
+            }
+
+            mInsetsState = insetsState;
+        }
+    };
+
 
     public ShellController(Context context,
             ShellInit shellInit,
             ShellCommandHandler shellCommandHandler,
+            DisplayInsetsController displayInsetsController,
             ShellExecutor mainExecutor) {
         mContext = context;
         mShellInit = shellInit;
         mShellCommandHandler = shellCommandHandler;
+        mDisplayInsetsController = displayInsetsController;
         mMainExecutor = mainExecutor;
         shellInit.addInitCallback(this::onInit, this);
     }
 
     private void onInit() {
         mShellCommandHandler.addDumpCallback(this::dump, this);
+        mDisplayInsetsController.addInsetsChangedListener(
+                mContext.getDisplayId(), mInsetsChangeListener);
     }
 
     /**
@@ -259,6 +302,25 @@
         }
     }
 
+    @VisibleForTesting
+    void onImeBoundsChanged(Rect bounds) {
+        ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Display Ime bounds changed");
+        mDisplayImeChangeListeners.forEach(
+                (DisplayImeChangeListener listener, Executor executor) ->
+                executor.execute(() -> listener.onImeBoundsChanged(
+                    mContext.getDisplayId(), bounds)));
+    }
+
+    @VisibleForTesting
+    void onImeVisibilityChanged(boolean isShowing) {
+        ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Display Ime visibility changed: isShowing=%b",
+                isShowing);
+        mDisplayImeChangeListeners.forEach(
+                (DisplayImeChangeListener listener, Executor executor) ->
+                executor.execute(() -> listener.onImeVisibilityChanged(
+                    mContext.getDisplayId(), isShowing)));
+    }
+
     private void handleInit() {
         SurfaceControlRegistry.createProcessInstance(mContext);
         mShellInit.init();
@@ -329,6 +391,19 @@
         }
 
         @Override
+        public void addDisplayImeChangeListener(DisplayImeChangeListener listener,
+                Executor executor) {
+            ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Adding new DisplayImeChangeListener");
+            mDisplayImeChangeListeners.put(listener, executor);
+        }
+
+        @Override
+        public void removeDisplayImeChangeListener(DisplayImeChangeListener listener) {
+            ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Removing DisplayImeChangeListener");
+            mDisplayImeChangeListeners.remove(listener);
+        }
+
+        @Override
         public boolean handleCommand(String[] args, PrintWriter pw) {
             try {
                 boolean[] result = new boolean[1];
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
index bc5dd11..bd1c64a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
@@ -25,6 +25,7 @@
 
 import java.io.PrintWriter;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 /**
  * General interface for notifying the Shell of common SysUI events like configuration or keyguard
@@ -65,6 +66,18 @@
     default void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {}
 
     /**
+     * Registers a DisplayImeChangeListener to monitor for changes on Ime
+     * position and visibility.
+     */
+    default void addDisplayImeChangeListener(DisplayImeChangeListener listener,
+            Executor executor) {}
+
+    /**
+     * Removes a registered DisplayImeChangeListener.
+     */
+    default void removeDisplayImeChangeListener(DisplayImeChangeListener listener) {}
+
+    /**
      * Handles a shell command.
      */
     default boolean handleCommand(final String[] args, PrintWriter pw) {
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/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
index ae39fbc..4a4c5e8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
@@ -37,6 +37,7 @@
 import com.android.wm.shell.bubbles.bar.BubbleBarLayerView
 import com.android.wm.shell.bubbles.properties.BubbleProperties
 import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayInsetsController
 import com.android.wm.shell.common.FloatingContentCoordinator
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.common.SyncTransactionQueue
@@ -94,7 +95,8 @@
         val windowManager = context.getSystemService(WindowManager::class.java)
         val shellInit = ShellInit(mainExecutor)
         val shellCommandHandler = ShellCommandHandler()
-        val shellController = ShellController(context, shellInit, shellCommandHandler, mainExecutor)
+        val shellController = ShellController(context, shellInit, shellCommandHandler,
+					      mock<DisplayInsetsController>(), mainExecutor)
         bubblePositioner = BubblePositioner(context, windowManager)
         val bubbleData =
             BubbleData(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
new file mode 100644
index 0000000..65117f7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.os.IBinder
+import android.testing.AndroidTestingRunner
+import android.view.SurfaceControl
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.view.WindowManager.TRANSIT_CLOSE
+import android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS
+import android.view.WindowManager.TRANSIT_NONE
+import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TRANSIT_SLEEP
+import android.view.WindowManager.TRANSIT_TO_BACK
+import android.view.WindowManager.TRANSIT_TO_FRONT
+import android.view.WindowManager.TRANSIT_WAKE
+import android.window.IWindowContainerToken
+import android.window.TransitionInfo
+import android.window.TransitionInfo.Change
+import android.window.WindowContainerToken
+import androidx.test.filters.SmallTest
+import com.android.modules.utils.testing.ExtendedMockitoRule
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.TransitionInfoBuilder
+import com.android.wm.shell.transition.Transitions
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.never
+import org.mockito.kotlin.same
+import org.mockito.kotlin.times
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesktopModeLoggerTransitionObserverTest {
+
+    @JvmField
+    @Rule
+    val extendedMockitoRule = ExtendedMockitoRule.Builder(this)
+            .mockStatic(DesktopModeEventLogger::class.java)
+            .mockStatic(DesktopModeStatus::class.java).build()!!
+
+    @Mock
+    lateinit var testExecutor: ShellExecutor
+    @Mock
+    private lateinit var mockShellInit: ShellInit
+    @Mock
+    private lateinit var transitions: Transitions
+
+    private lateinit var transitionObserver: DesktopModeLoggerTransitionObserver
+    private lateinit var shellInit: ShellInit
+    private lateinit var desktopModeEventLogger: DesktopModeEventLogger
+
+    @Before
+    fun setup() {
+        Mockito.`when`(DesktopModeStatus.isEnabled()).thenReturn(true)
+        shellInit = Mockito.spy(ShellInit(testExecutor))
+        desktopModeEventLogger = mock(DesktopModeEventLogger::class.java)
+
+        transitionObserver = DesktopModeLoggerTransitionObserver(
+            mockShellInit, transitions, desktopModeEventLogger)
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            val initRunnableCaptor = ArgumentCaptor.forClass(
+                Runnable::class.java)
+            verify(mockShellInit).addInitCallback(initRunnableCaptor.capture(),
+                same(transitionObserver))
+            initRunnableCaptor.value.run()
+        } else {
+            transitionObserver.onInit()
+        }
+    }
+
+    @Test
+    fun testRegistersObserverAtInit() {
+        verify(transitions)
+                .registerObserver(same(
+                    transitionObserver))
+    }
+
+    @Test
+    fun taskCreated_notFreeformWindow_doesNotLogSessionEnterOrTaskAdded() {
+        val change = createChange(TRANSIT_OPEN, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
+        val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
+
+        callOnTransitionReady(transitionInfo)
+
+        verify(desktopModeEventLogger, never()).logSessionEnter(any(), any())
+        verify(desktopModeEventLogger, never()).logTaskAdded(any(), any())
+    }
+
+    @Test
+    fun taskCreated_FreeformWindowOpen_logSessionEnterAndTaskAdded() {
+        val change = createChange(TRANSIT_OPEN, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+        val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
+
+        callOnTransitionReady(transitionInfo)
+        val sessionId = transitionObserver.getLoggerSessionId()
+
+        assertThat(sessionId).isNotNull()
+        verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!),
+            eq(EnterReason.APP_FREEFORM_INTENT))
+        verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+    }
+
+    @Test
+    fun taskChanged_taskMovedToDesktopByDrag_logSessionEnterAndTaskAdded() {
+        val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+        // task change is finalised when drag ends
+        val transitionInfo = TransitionInfoBuilder(
+            Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, 0).addChange(change).build()
+
+        callOnTransitionReady(transitionInfo)
+        val sessionId = transitionObserver.getLoggerSessionId()
+
+        assertThat(sessionId).isNotNull()
+        verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!),
+            eq(EnterReason.APP_HANDLE_DRAG))
+        verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+    }
+
+    @Test
+    fun taskChanged_taskMovedToDesktopByButtonTap_logSessionEnterAndTaskAdded() {
+        val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+        val transitionInfo = TransitionInfoBuilder(Transitions.TRANSIT_MOVE_TO_DESKTOP, 0)
+                .addChange(change).build()
+
+        callOnTransitionReady(transitionInfo)
+        val sessionId = transitionObserver.getLoggerSessionId()
+
+        assertThat(sessionId).isNotNull()
+        verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!),
+            eq(EnterReason.APP_HANDLE_MENU_BUTTON))
+        verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+    }
+
+    @Test
+    fun taskChanged_existingFreeformTaskMadeVisible_logSessionEnterAndTaskAdded() {
+        val taskInfo = createTaskInfo(1, WINDOWING_MODE_FREEFORM)
+        taskInfo.isVisibleRequested = true
+        val change = createChange(TRANSIT_CHANGE, taskInfo)
+        val transitionInfo = TransitionInfoBuilder(Transitions.TRANSIT_MOVE_TO_DESKTOP, 0)
+                .addChange(change).build()
+
+        callOnTransitionReady(transitionInfo)
+        val sessionId = transitionObserver.getLoggerSessionId()
+
+        assertThat(sessionId).isNotNull()
+        verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!),
+            eq(EnterReason.APP_HANDLE_MENU_BUTTON))
+        verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+    }
+
+    @Test
+    fun taskToFront_screenWake_logSessionStartedAndTaskAdded() {
+        val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+        val transitionInfo = TransitionInfoBuilder(TRANSIT_WAKE, 0)
+                .addChange(change).build()
+
+        callOnTransitionReady(transitionInfo)
+        val sessionId = transitionObserver.getLoggerSessionId()
+
+        assertThat(sessionId).isNotNull()
+        verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!),
+            eq(EnterReason.SCREEN_ON))
+        verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+    }
+
+    @Test
+    fun freeformTaskVisible_screenTurnOff_logSessionExitAndTaskRemoved_sessionIdNull() {
+        val sessionId = 1
+        // add a freeform task
+        transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+        transitionObserver.setLoggerSessionId(sessionId)
+
+        val transitionInfo = TransitionInfoBuilder(TRANSIT_SLEEP).build()
+        callOnTransitionReady(transitionInfo)
+
+        verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+        verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId),
+            eq(ExitReason.SCREEN_OFF))
+        assertThat(transitionObserver.getLoggerSessionId()).isNull()
+    }
+
+    @Test
+    fun freeformTaskVisible_exitDesktopUsingDrag_logSessionExitAndTaskRemoved_sessionIdNull() {
+        val sessionId = 1
+        // add a freeform task
+        transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+        transitionObserver.setLoggerSessionId(sessionId)
+
+        // window mode changing from FREEFORM to FULLSCREEN
+        val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
+        val transitionInfo = TransitionInfoBuilder(Transitions.TRANSIT_EXIT_DESKTOP_MODE)
+                .addChange(change).build()
+        callOnTransitionReady(transitionInfo)
+
+        verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+        verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId),
+            eq(ExitReason.DRAG_TO_EXIT))
+        assertThat(transitionObserver.getLoggerSessionId()).isNull()
+    }
+
+    @Test
+    fun freeformTaskVisible_exitDesktopBySwipeUp_logSessionExitAndTaskRemoved_sessionIdNull() {
+        val sessionId = 1
+        // add a freeform task
+        transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+        transitionObserver.setLoggerSessionId(sessionId)
+
+        // recents transition
+        val change = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+        val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
+                .addChange(change).build()
+        callOnTransitionReady(transitionInfo)
+
+        verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+        verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId),
+            eq(ExitReason.RETURN_HOME_OR_OVERVIEW))
+        assertThat(transitionObserver.getLoggerSessionId()).isNull()
+    }
+
+    @Test
+    fun freeformTaskVisible_taskFinished_logSessionExitAndTaskRemoved_sessionIdNull() {
+        val sessionId = 1
+        // add a freeform task
+        transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+        transitionObserver.setLoggerSessionId(sessionId)
+
+        // task closing
+        val change = createChange(TRANSIT_CLOSE, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
+        val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE).addChange(change).build()
+        callOnTransitionReady(transitionInfo)
+
+        verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+        verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId),
+            eq(ExitReason.TASK_FINISHED))
+        assertThat(transitionObserver.getLoggerSessionId()).isNull()
+    }
+
+    @Test
+    fun sessionExitByRecents_cancelledAnimation_sessionRestored() {
+        val sessionId = 1
+        // add a freeform task to an existing session
+        transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+        transitionObserver.setLoggerSessionId(sessionId)
+
+        // recents transition sent freeform window to back
+        val change = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+        val transitionInfo1 =
+            TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS).addChange(change)
+                    .build()
+        callOnTransitionReady(transitionInfo1)
+        verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+        verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId),
+            eq(ExitReason.RETURN_HOME_OR_OVERVIEW))
+        assertThat(transitionObserver.getLoggerSessionId()).isNull()
+
+        val transitionInfo2 = TransitionInfoBuilder(TRANSIT_NONE).build()
+        callOnTransitionReady(transitionInfo2)
+
+        verify(desktopModeEventLogger, times(1)).logSessionEnter(any(), any())
+        verify(desktopModeEventLogger, times(1)).logTaskAdded(any(), any())
+    }
+
+    @Test
+    fun sessionAlreadyStarted_newFreeformTaskAdded_logsTaskAdded() {
+        val sessionId = 1
+        // add an existing freeform task
+        transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+        transitionObserver.setLoggerSessionId(sessionId)
+
+        // new freeform task added
+        val change = createChange(TRANSIT_OPEN, createTaskInfo(2, WINDOWING_MODE_FREEFORM))
+        val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
+        callOnTransitionReady(transitionInfo)
+
+        verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+        verify(desktopModeEventLogger, never()).logSessionEnter(any(), any())
+    }
+
+    @Test
+    fun sessionAlreadyStarted_freeformTaskRemoved_logsTaskRemoved() {
+        val sessionId = 1
+        // add two existing freeform tasks
+        transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+        transitionObserver.addTaskInfosToCachedMap(createTaskInfo(2, WINDOWING_MODE_FREEFORM))
+        transitionObserver.setLoggerSessionId(sessionId)
+
+        // new freeform task added
+        val change = createChange(TRANSIT_CLOSE, createTaskInfo(2, WINDOWING_MODE_FREEFORM))
+        val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE, 0).addChange(change).build()
+        callOnTransitionReady(transitionInfo)
+
+        verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+        verify(desktopModeEventLogger, never()).logSessionExit(any(), any())
+    }
+
+    /**
+     * Simulate calling the onTransitionReady() method
+     */
+    private fun callOnTransitionReady(transitionInfo: TransitionInfo) {
+        val transition = mock(IBinder::class.java)
+        val startT = mock(
+            SurfaceControl.Transaction::class.java)
+        val finishT = mock(
+            SurfaceControl.Transaction::class.java)
+
+        transitionObserver.onTransitionReady(transition, transitionInfo, startT, finishT)
+    }
+
+    companion object {
+        fun createTaskInfo(taskId: Int, windowMode: Int): ActivityManager.RunningTaskInfo {
+            val taskInfo = ActivityManager.RunningTaskInfo()
+            taskInfo.taskId = taskId
+            taskInfo.configuration.windowConfiguration.windowingMode = windowMode
+
+            return taskInfo
+        }
+
+        fun createChange(mode: Int, taskInfo: ActivityManager.RunningTaskInfo): Change {
+            val change = Change(
+                WindowContainerToken(mock(
+                    IWindowContainerToken::class.java)),
+                mock(SurfaceControl::class.java))
+            change.mode = mode
+            change.taskInfo = taskInfo
+            return change
+        }
+    }
+}
\ No newline at end of file
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 0136751..5df9dd3 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
@@ -87,6 +87,7 @@
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.verify
+import org.mockito.kotlin.times
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.quality.Strictness
 
@@ -113,6 +114,7 @@
     @Mock lateinit var recentsTransitionHandler: RecentsTransitionHandler
     @Mock lateinit var dragAndDropController: DragAndDropController
     @Mock lateinit var multiInstanceHelper: MultiInstanceHelper
+    @Mock lateinit var desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver
 
     private lateinit var mockitoSession: StaticMockitoSession
     private lateinit var controller: DesktopTasksController
@@ -163,6 +165,7 @@
             mToggleResizeDesktopTaskTransitionHandler,
             dragToDesktopTransitionHandler,
             desktopModeTaskRepository,
+            desktopModeLoggerTransitionObserver,
             launchAdjacentController,
             recentsTransitionHandler,
             multiInstanceHelper,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 3384509..d38fc6c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -129,7 +129,7 @@
         }).when(mMockExecutor).execute(any());
         mShellInit = spy(new ShellInit(mMockExecutor));
         mShellController = spy(new ShellController(mContext, mShellInit, mMockShellCommandHandler,
-                mMockExecutor));
+                mMockDisplayInsetsController, mMockExecutor));
         mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler,
                 mShellController, mMockDisplayController, mMockPipAnimationController,
                 mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 10e9e11..41a4e8d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -58,6 +58,7 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
@@ -96,6 +97,8 @@
     private DesktopModeTaskRepository mDesktopModeTaskRepository;
     @Mock
     private ActivityTaskManager mActivityTaskManager;
+    @Mock
+    private DisplayInsetsController mDisplayInsetsController;
 
     private ShellTaskOrganizer mShellTaskOrganizer;
     private RecentTasksController mRecentTasksController;
@@ -110,7 +113,7 @@
         when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
         mShellInit = spy(new ShellInit(mMainExecutor));
         mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler,
-                mMainExecutor));
+                mDisplayInsetsController, mMainExecutor));
         mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit,
                 mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
                 Optional.of(mDesktopModeTaskRepository), mMainExecutor);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 315d97e..3c387f0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -123,7 +123,7 @@
         assumeTrue(ActivityTaskManager.supportsSplitScreenMultiWindow(mContext));
         MockitoAnnotations.initMocks(this);
         mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler,
-                mMainExecutor));
+                mDisplayInsetsController, mMainExecutor));
         mSplitScreenController = spy(new SplitScreenController(mContext, mShellInit,
                 mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
                 mRootTDAOrganizer, mDisplayController, mDisplayImeController,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
index 012c4081..ff76a2f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
@@ -40,6 +40,7 @@
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -65,6 +66,7 @@
 
     private @Mock Context mContext;
     private @Mock DisplayManager mDisplayManager;
+    private @Mock DisplayInsetsController mDisplayInsetsController;
     private @Mock ShellCommandHandler mShellCommandHandler;
     private @Mock ShellTaskOrganizer mTaskOrganizer;
     private @Mock ShellExecutor mMainExecutor;
@@ -83,7 +85,7 @@
         doReturn(super.mContext.getResources()).when(mContext).getResources();
         mShellInit = spy(new ShellInit(mMainExecutor));
         mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler,
-                mMainExecutor));
+                mDisplayInsetsController, mMainExecutor));
         mController = new StartingWindowController(mContext, mShellInit, mShellController,
                 mTaskOrganizer, mMainExecutor, mTypeAlgorithm, mIconProvider, mTransactionPool);
         mShellInit.init();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
index 7c520c3..6292018 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
+import android.graphics.Rect;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -35,8 +36,8 @@
 
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.ExternalInterfaceBinder;
-import com.android.wm.shell.common.ShellExecutor;
 
 import org.junit.After;
 import org.junit.Before;
@@ -63,12 +64,15 @@
     private ShellCommandHandler mShellCommandHandler;
     @Mock
     private Context mTestUserContext;
+    @Mock
+    private DisplayInsetsController mDisplayInsetsController;
 
     private TestShellExecutor mExecutor;
     private ShellController mController;
     private TestConfigurationChangeListener mConfigChangeListener;
     private TestKeyguardChangeListener mKeyguardChangeListener;
     private TestUserChangeListener mUserChangeListener;
+    private TestDisplayImeChangeListener mDisplayImeChangeListener;
 
 
     @Before
@@ -77,8 +81,10 @@
         mKeyguardChangeListener = new TestKeyguardChangeListener();
         mConfigChangeListener = new TestConfigurationChangeListener();
         mUserChangeListener = new TestUserChangeListener();
+        mDisplayImeChangeListener = new TestDisplayImeChangeListener();
         mExecutor = new TestShellExecutor();
-        mController = new ShellController(mContext, mShellInit, mShellCommandHandler, mExecutor);
+        mController = new ShellController(mContext, mShellInit, mShellCommandHandler,
+                mDisplayInsetsController, mExecutor);
         mController.onConfigurationChanged(getConfigurationCopy());
     }
 
@@ -130,6 +136,45 @@
     }
 
     @Test
+    public void testAddDisplayImeChangeListener_ensureCallback() {
+        mController.asShell().addDisplayImeChangeListener(
+                mDisplayImeChangeListener, mExecutor);
+
+        final Rect bounds = new Rect(10, 20, 30, 40);
+        mController.onImeBoundsChanged(bounds);
+        mController.onImeVisibilityChanged(true);
+        mExecutor.flushAll();
+
+        assertTrue(mDisplayImeChangeListener.boundsChanged == 1);
+        assertTrue(bounds.equals(mDisplayImeChangeListener.lastBounds));
+        assertTrue(mDisplayImeChangeListener.visibilityChanged == 1);
+        assertTrue(mDisplayImeChangeListener.lastVisibility);
+    }
+
+    @Test
+    public void testDoubleAddDisplayImeChangeListener_ensureSingleCallback() {
+        mController.asShell().addDisplayImeChangeListener(
+                mDisplayImeChangeListener, mExecutor);
+        mController.asShell().addDisplayImeChangeListener(
+                mDisplayImeChangeListener, mExecutor);
+
+        mController.onImeVisibilityChanged(true);
+        mExecutor.flushAll();
+        assertTrue(mDisplayImeChangeListener.visibilityChanged == 1);
+    }
+
+    @Test
+    public void testAddRemoveDisplayImeChangeListener_ensureNoCallback() {
+        mController.asShell().addDisplayImeChangeListener(
+                mDisplayImeChangeListener, mExecutor);
+        mController.asShell().removeDisplayImeChangeListener(mDisplayImeChangeListener);
+
+        mController.onImeVisibilityChanged(true);
+        mExecutor.flushAll();
+        assertTrue(mDisplayImeChangeListener.visibilityChanged == 0);
+    }
+
+    @Test
     public void testAddUserChangeListener_ensureCallback() {
         mController.addUserChangeListener(mUserChangeListener);
 
@@ -457,4 +502,23 @@
             lastUserProfiles = profiles;
         }
     }
+
+    private static class TestDisplayImeChangeListener implements DisplayImeChangeListener {
+        public int boundsChanged = 0;
+        public Rect lastBounds;
+        public int visibilityChanged = 0;
+        public boolean lastVisibility = false;
+
+        @Override
+        public void onImeBoundsChanged(int displayId, Rect bounds) {
+            boundsChanged++;
+            lastBounds = bounds;
+        }
+
+        @Override
+        public void onImeVisibilityChanged(int displayId, boolean isShowing) {
+            visibilityChanged++;
+            lastVisibility = isShowing;
+        }
+    }
 }
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/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/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index 156be38..f33bcb7 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -11,7 +11,7 @@
     name: "location_bypass"
     namespace: "location"
     description: "Enable location bypass appops behavior"
-    bug: "301150056"
+    bug: "329151785"
 }
 
 flag {
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 6cf9c6f..bf39425 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -109,5 +109,5 @@
     name: "enable_null_session_in_media_browser_service"
     namespace: "media_solutions"
     description: "Enables apps owning a MediaBrowserService to disconnect all connected browsers."
-    bug: "263520343"
+    bug: "185136506"
 }
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/Android.bp b/nfc/Android.bp
index 7dd16ba..7698e2b 100644
--- a/nfc/Android.bp
+++ b/nfc/Android.bp
@@ -76,6 +76,9 @@
         "//apex_available:platform",
         "com.android.nfcservices",
     ],
+    aconfig_declarations: [
+        "android.nfc.flags-aconfig",
+    ],
 }
 
 filegroup {
diff --git a/packages/CrashRecovery/OWNERS b/packages/CrashRecovery/OWNERS
index daa0211..8337fd2 100644
--- a/packages/CrashRecovery/OWNERS
+++ b/packages/CrashRecovery/OWNERS
@@ -1,3 +1 @@
-ancr@google.com
-harshitmahajan@google.com
-robertogil@google.com
+include /services/core/java/com/android/server/crashrecovery/OWNERS
\ No newline at end of file
diff --git a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
index 5d71b7d..37b5d40 100644
--- a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
@@ -38,15 +38,15 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
-import android.util.BackgroundThread;
-import android.util.LongArrayQueue;
 import android.util.Slog;
 import android.util.Xml;
+import android.utils.BackgroundThread;
+import android.utils.LongArrayQueue;
+import android.utils.XmlUtils;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.XmlUtils;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 
diff --git a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
index 9217e70..d0fee44 100644
--- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
@@ -29,7 +29,6 @@
 import android.content.pm.VersionedPackage;
 import android.os.Build;
 import android.os.Environment;
-import android.os.FileUtils;
 import android.os.PowerManager;
 import android.os.RecoverySystem;
 import android.os.SystemClock;
@@ -42,10 +41,11 @@
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
+import android.utils.ArrayUtils;
+import android.utils.FileUtils;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
 import com.android.server.PackageWatchdog.FailureReasons;
 import com.android.server.PackageWatchdog.PackageHealthObserver;
 import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
@@ -707,7 +707,7 @@
                 if (pm.getModuleInfo(packageName, 0) != null) {
                     return true;
                 }
-            } catch (PackageManager.NameNotFoundException ignore) {
+            } catch (PackageManager.NameNotFoundException | IllegalStateException ignore) {
             }
 
             return isPersistentSystemApp(packageName);
diff --git a/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java b/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java
new file mode 100644
index 0000000..fa4d6af
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java
@@ -0,0 +1,115 @@
+/*
+ * 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.utils;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.io.File;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Copied over from frameworks/base/core/java/com/android/internal/util/ArrayUtils.java
+ *
+ * @hide
+ */
+public class ArrayUtils {
+    private ArrayUtils() { /* cannot be instantiated */ }
+    public static final File[] EMPTY_FILE = new File[0];
+
+
+    /**
+     * Return first index of {@code value} in {@code array}, or {@code -1} if
+     * not found.
+     */
+    public static <T> int indexOf(@Nullable T[] array, T value) {
+        if (array == null) return -1;
+        for (int i = 0; i < array.length; i++) {
+            if (Objects.equals(array[i], value)) return i;
+        }
+        return -1;
+    }
+
+    /** @hide */
+    public static @NonNull File[] defeatNullable(@Nullable File[] val) {
+        return (val != null) ? val : EMPTY_FILE;
+    }
+
+    /**
+     * Checks if given array is null or has zero elements.
+     */
+    public static boolean isEmpty(@Nullable int[] array) {
+        return array == null || array.length == 0;
+    }
+
+    /**
+     * True if the byte array is null or has length 0.
+     */
+    public static boolean isEmpty(@Nullable byte[] array) {
+        return array == null || array.length == 0;
+    }
+
+    /**
+     * Converts from List of bytes to byte array
+     * @param list
+     * @return byte[]
+     */
+    public static byte[] toPrimitive(List<byte[]> list) {
+        if (list.size() == 0) {
+            return new byte[0];
+        }
+        int byteLen = list.get(0).length;
+        byte[] array = new byte[list.size() * byteLen];
+        for (int i = 0; i < list.size(); i++) {
+            for (int j = 0; j < list.get(i).length; j++) {
+                array[i * byteLen + j] = list.get(i)[j];
+            }
+        }
+        return array;
+    }
+
+    /**
+     * Adds value to given array if not already present, providing set-like
+     * behavior.
+     */
+    public static @NonNull int[] appendInt(@Nullable int[] cur, int val) {
+        return appendInt(cur, val, false);
+    }
+
+    /**
+     * Adds value to given array.
+     */
+    public static @NonNull int[] appendInt(@Nullable int[] cur, int val,
+            boolean allowDuplicates) {
+        if (cur == null) {
+            return new int[] { val };
+        }
+        final int n = cur.length;
+        if (!allowDuplicates) {
+            for (int i = 0; i < n; i++) {
+                if (cur[i] == val) {
+                    return cur;
+                }
+            }
+        }
+        int[] ret = new int[n + 1];
+        System.arraycopy(cur, 0, ret, 0, n);
+        ret[n] = val;
+        return ret;
+    }
+}
diff --git a/packages/CrashRecovery/services/java/com/android/util/BackgroundThread.java b/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java
similarity index 99%
rename from packages/CrashRecovery/services/java/com/android/util/BackgroundThread.java
rename to packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java
index a6ae68f..afcf689 100644
--- a/packages/CrashRecovery/services/java/com/android/util/BackgroundThread.java
+++ b/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.util;
+package android.utils;
 
 import android.annotation.NonNull;
 import android.os.Handler;
diff --git a/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java b/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java
new file mode 100644
index 0000000..e4923bf
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java
@@ -0,0 +1,128 @@
+/*
+ * 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.utils;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Bits and pieces copied from hidden API of android.os.FileUtils.
+ *
+ * @hide
+ */
+public class FileUtils {
+    /**
+     * Read a text file into a String, optionally limiting the length.
+     *
+     * @param file     to read (will not seek, so things like /proc files are OK)
+     * @param max      length (positive for head, negative of tail, 0 for no limit)
+     * @param ellipsis to add of the file was truncated (can be null)
+     * @return the contents of the file, possibly truncated
+     * @throws IOException if something goes wrong reading the file
+     * @hide
+     */
+    public static @Nullable String readTextFile(@Nullable File file, @Nullable int max,
+            @Nullable String ellipsis) throws IOException {
+        InputStream input = new FileInputStream(file);
+        // wrapping a BufferedInputStream around it because when reading /proc with unbuffered
+        // input stream, bytes read not equal to buffer size is not necessarily the correct
+        // indication for EOF; but it is true for BufferedInputStream due to its implementation.
+        BufferedInputStream bis = new BufferedInputStream(input);
+        try {
+            long size = file.length();
+            if (max > 0 || (size > 0 && max == 0)) {  // "head" mode: read the first N bytes
+                if (size > 0 && (max == 0 || size < max)) max = (int) size;
+                byte[] data = new byte[max + 1];
+                int length = bis.read(data);
+                if (length <= 0) return "";
+                if (length <= max) return new String(data, 0, length);
+                if (ellipsis == null) return new String(data, 0, max);
+                return new String(data, 0, max) + ellipsis;
+            } else if (max < 0) {  // "tail" mode: keep the last N
+                int len;
+                boolean rolled = false;
+                byte[] last = null;
+                byte[] data = null;
+                do {
+                    if (last != null) rolled = true;
+                    byte[] tmp = last;
+                    last = data;
+                    data = tmp;
+                    if (data == null) data = new byte[-max];
+                    len = bis.read(data);
+                } while (len == data.length);
+
+                if (last == null && len <= 0) return "";
+                if (last == null) return new String(data, 0, len);
+                if (len > 0) {
+                    rolled = true;
+                    System.arraycopy(last, len, last, 0, last.length - len);
+                    System.arraycopy(data, 0, last, last.length - len, len);
+                }
+                if (ellipsis == null || !rolled) return new String(last);
+                return ellipsis + new String(last);
+            } else {  // "cat" mode: size unknown, read it all in streaming fashion
+                ByteArrayOutputStream contents = new ByteArrayOutputStream();
+                int len;
+                byte[] data = new byte[1024];
+                do {
+                    len = bis.read(data);
+                    if (len > 0) contents.write(data, 0, len);
+                } while (len == data.length);
+                return contents.toString();
+            }
+        } finally {
+            bis.close();
+            input.close();
+        }
+    }
+
+    /**
+     * Perform an fsync on the given FileOutputStream. The stream at this
+     * point must be flushed but not yet closed.
+     *
+     * @hide
+     */
+    public static boolean sync(FileOutputStream stream) {
+        try {
+            if (stream != null) {
+                stream.getFD().sync();
+            }
+            return true;
+        } catch (IOException e) {
+        }
+        return false;
+    }
+
+    /**
+     * List the files in the directory or return empty file.
+     *
+     * @hide
+     */
+    public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) {
+        return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles())
+            : ArrayUtils.EMPTY_FILE;
+    }
+}
diff --git a/packages/CrashRecovery/services/java/com/android/util/HandlerExecutor.java b/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java
similarity index 98%
rename from packages/CrashRecovery/services/java/com/android/util/HandlerExecutor.java
rename to packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java
index 948ebcca..fdb15e2 100644
--- a/packages/CrashRecovery/services/java/com/android/util/HandlerExecutor.java
+++ b/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.util;
+package android.utils;
 
 import android.annotation.NonNull;
 import android.os.Handler;
diff --git a/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java b/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java
new file mode 100644
index 0000000..5cdc253
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java
@@ -0,0 +1,188 @@
+/*
+ * 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.utils;
+
+import libcore.util.EmptyArray;
+
+import java.util.NoSuchElementException;
+
+/**
+ * Copied from frameworks/base/core/java/android/util/LongArrayQueue.java
+ *
+ * @hide
+ */
+public class LongArrayQueue {
+
+    private long[] mValues;
+    private int mSize;
+    private int mHead;
+    private int mTail;
+
+    private long[] newUnpaddedLongArray(int num) {
+        return new long[num];
+    }
+    /**
+     * Initializes a queue with the given starting capacity.
+     *
+     * @param initialCapacity the capacity.
+     */
+    public LongArrayQueue(int initialCapacity) {
+        if (initialCapacity == 0) {
+            mValues = EmptyArray.LONG;
+        } else {
+            mValues = newUnpaddedLongArray(initialCapacity);
+        }
+        mSize = 0;
+        mHead = mTail = 0;
+    }
+
+    /**
+     * Initializes a queue with default starting capacity.
+     */
+    public LongArrayQueue() {
+        this(16);
+    }
+
+    /** @hide */
+    public static int growSize(int currentSize) {
+        return currentSize <= 4 ? 8 : currentSize * 2;
+    }
+
+    private void grow() {
+        if (mSize < mValues.length) {
+            throw new IllegalStateException("Queue not full yet!");
+        }
+        final int newSize = growSize(mSize);
+        final long[] newArray = newUnpaddedLongArray(newSize);
+        final int r = mValues.length - mHead; // Number of elements on and to the right of head.
+        System.arraycopy(mValues, mHead, newArray, 0, r);
+        System.arraycopy(mValues, 0, newArray, r, mHead);
+        mValues = newArray;
+        mHead = 0;
+        mTail = mSize;
+    }
+
+    /**
+     * Returns the number of elements in the queue.
+     */
+    public int size() {
+        return mSize;
+    }
+
+    /**
+     * Removes all elements from this queue.
+     */
+    public void clear() {
+        mSize = 0;
+        mHead = mTail = 0;
+    }
+
+    /**
+     * Adds a value to the tail of the queue.
+     *
+     * @param value the value to be added.
+     */
+    public void addLast(long value) {
+        if (mSize == mValues.length) {
+            grow();
+        }
+        mValues[mTail] = value;
+        mTail = (mTail + 1) % mValues.length;
+        mSize++;
+    }
+
+    /**
+     * Removes an element from the head of the queue.
+     *
+     * @return the element at the head of the queue.
+     * @throws NoSuchElementException if the queue is empty.
+     */
+    public long removeFirst() {
+        if (mSize == 0) {
+            throw new NoSuchElementException("Queue is empty!");
+        }
+        final long ret = mValues[mHead];
+        mHead = (mHead + 1) % mValues.length;
+        mSize--;
+        return ret;
+    }
+
+    /**
+     * Returns the element at the given position from the head of the queue, where 0 represents the
+     * head of the queue.
+     *
+     * @param position the position from the head of the queue.
+     * @return the element found at the given position.
+     * @throws IndexOutOfBoundsException if {@code position} < {@code 0} or
+     *                                   {@code position} >= {@link #size()}
+     */
+    public long get(int position) {
+        if (position < 0 || position >= mSize) {
+            throw new IndexOutOfBoundsException("Index " + position
+                + " not valid for a queue of size " + mSize);
+        }
+        final int index = (mHead + position) % mValues.length;
+        return mValues[index];
+    }
+
+    /**
+     * Returns the element at the head of the queue, without removing it.
+     *
+     * @return the element at the head of the queue.
+     * @throws NoSuchElementException if the queue is empty
+     */
+    public long peekFirst() {
+        if (mSize == 0) {
+            throw new NoSuchElementException("Queue is empty!");
+        }
+        return mValues[mHead];
+    }
+
+    /**
+     * Returns the element at the tail of the queue.
+     *
+     * @return the element at the tail of the queue.
+     * @throws NoSuchElementException if the queue is empty.
+     */
+    public long peekLast() {
+        if (mSize == 0) {
+            throw new NoSuchElementException("Queue is empty!");
+        }
+        final int index = (mTail == 0) ? mValues.length - 1 : mTail - 1;
+        return mValues[index];
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String toString() {
+        if (mSize <= 0) {
+            return "{}";
+        }
+
+        final StringBuilder buffer = new StringBuilder(mSize * 64);
+        buffer.append('{');
+        buffer.append(get(0));
+        for (int i = 1; i < mSize; i++) {
+            buffer.append(", ");
+            buffer.append(get(i));
+        }
+        buffer.append('}');
+        return buffer.toString();
+    }
+}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java b/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java
new file mode 100644
index 0000000..dbbef61
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java
@@ -0,0 +1,118 @@
+/*
+ * 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.utils;
+
+import android.annotation.NonNull;
+import android.system.ErrnoException;
+import android.system.Os;
+
+import com.android.modules.utils.TypedXmlPullParser;
+
+import libcore.util.XmlObjectFactory;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Copied over partly from frameworks/base/core/java/com/android/internal/util/XmlUtils.java
+ *
+ * @hide
+ */
+public class XmlUtils {
+
+    private static final String STRING_ARRAY_SEPARATOR = ":";
+
+    /** @hide */
+    public static final void beginDocument(XmlPullParser parser, String firstElementName)
+            throws XmlPullParserException, IOException {
+        int type;
+        while ((type = parser.next()) != parser.START_TAG
+            && type != parser.END_DOCUMENT) {
+            // Do nothing
+        }
+
+        if (type != parser.START_TAG) {
+            throw new XmlPullParserException("No start tag found");
+        }
+
+        if (!parser.getName().equals(firstElementName)) {
+            throw new XmlPullParserException("Unexpected start tag: found " + parser.getName()
+                + ", expected " + firstElementName);
+        }
+    }
+
+    /** @hide */
+    public static boolean nextElementWithin(XmlPullParser parser, int outerDepth)
+            throws IOException, XmlPullParserException {
+        for (;;) {
+            int type = parser.next();
+            if (type == XmlPullParser.END_DOCUMENT
+                    || (type == XmlPullParser.END_TAG && parser.getDepth() == outerDepth)) {
+                return false;
+            }
+            if (type == XmlPullParser.START_TAG
+                    && parser.getDepth() == outerDepth + 1) {
+                return true;
+            }
+        }
+    }
+
+    private static XmlPullParser newPullParser() {
+        try {
+            XmlPullParser parser = XmlObjectFactory.newXmlPullParser();
+            parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true);
+            parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+            return parser;
+        } catch (XmlPullParserException e) {
+            throw new AssertionError();
+        }
+    }
+
+    /** @hide */
+    public static @NonNull TypedXmlPullParser resolvePullParser(@NonNull InputStream in)
+            throws IOException {
+        final byte[] magic = new byte[4];
+        if (in instanceof FileInputStream) {
+            try {
+                Os.pread(((FileInputStream) in).getFD(), magic, 0, magic.length, 0);
+            } catch (ErrnoException e) {
+                throw e.rethrowAsIOException();
+            }
+        } else {
+            if (!in.markSupported()) {
+                in = new BufferedInputStream(in);
+            }
+            in.mark(8);
+            in.read(magic);
+            in.reset();
+        }
+
+        final TypedXmlPullParser xml;
+        xml = (TypedXmlPullParser) newPullParser();
+        try {
+            xml.setInput(in, "UTF_8");
+        } catch (XmlPullParserException e) {
+            throw new IOException(e);
+        }
+        return xml;
+    }
+}
diff --git a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenImagePathManager.kt b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenPathManager.kt
similarity index 77%
rename from packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenImagePathManager.kt
rename to packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenPathManager.kt
index 6aef24d..9cfdffd 100644
--- a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenImagePathManager.kt
+++ b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenPathManager.kt
@@ -18,34 +18,34 @@
 
 import android.os.Build
 import androidx.test.platform.app.InstrumentationRegistry
-import platform.test.screenshot.GoldenImagePathManager
+import platform.test.screenshot.GoldenPathManager
 import platform.test.screenshot.PathConfig
 
 /** The assets path to be used by all CredentialManager screenshot tests. */
 private const val ASSETS_PREFIX = "frameworks/base/packages/CredentialManager"
 private const val ASSETS_PATH = "${ASSETS_PREFIX}/tests/robotests/screenshot/customization/assets"
-private const val ASSETS_PATH_ROBO =
-        "${ASSETS_PREFIX}/tests/robotests/customization/assets"
+private const val ASSETS_PATH_ROBO = "${ASSETS_PREFIX}/tests/robotests/customization/assets"
 
 private val isRobolectric = Build.FINGERPRINT.contains("robolectric")
 
-class CredentialManagerGoldenImagePathManager(
-        pathConfig: PathConfig,
-        assetsPathRelativeToBuildRoot: String = if (isRobolectric) ASSETS_PATH_ROBO else ASSETS_PATH
-) : GoldenImagePathManager(
+class CredentialManagerGoldenPathManager(
+    pathConfig: PathConfig,
+    assetsPathRelativeToBuildRoot: String = if (isRobolectric) ASSETS_PATH_ROBO else ASSETS_PATH
+) :
+    GoldenPathManager(
         appContext = InstrumentationRegistry.getInstrumentation().context,
         assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
         deviceLocalPath =
-        InstrumentationRegistry.getInstrumentation()
+            InstrumentationRegistry.getInstrumentation()
                 .targetContext
                 .filesDir
                 .absolutePath
                 .toString() + "/credman_screenshots",
         pathConfig = pathConfig,
-) {
+    ) {
     override fun toString(): String {
         // This string is appended to all actual/expected screenshots on the device, so make sure
         // it is a static value.
-        return "CredentialManagerGoldenImagePathManager"
+        return "CredentialManagerGoldenPathManager"
     }
-}
\ No newline at end of file
+}
diff --git a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt
index 28d83ee..b843213 100644
--- a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt
+++ b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt
@@ -60,7 +60,7 @@
     @get:Rule
     val screenshotRule = ComposeScreenshotTestRule(
             emulationSpec,
-            CredentialManagerGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec))
+            CredentialManagerGoldenPathManager(getEmulatedDevicePathConfig(emulationSpec))
     )
 
     @get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule()
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
index e79176b..56b1c2e 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
@@ -56,7 +56,7 @@
         headerContent = {
             SignInHeader(
                 icon = entry.icon,
-                title = stringResource(R.string.use_password_title),
+                title = stringResource(R.string.use_passkey_title),
             )
         },
         accountContent = {
diff --git a/packages/PackageInstaller/res/values-watch/themes.xml b/packages/PackageInstaller/res/values-watch/themes.xml
index 5e52008..814d08a 100644
--- a/packages/PackageInstaller/res/values-watch/themes.xml
+++ b/packages/PackageInstaller/res/values-watch/themes.xml
@@ -16,5 +16,11 @@
   -->
 
 <resources>
-    <style name="DialogWhenLarge" parent="@android:style/Theme.DeviceDefault.NoActionBar"/>
+    <style name="Theme.AlertDialogActivity"
+        parent="@android:style/Theme.DeviceDefault.Dialog.Alert">
+        <item name="alertDialogStyle">@style/AlertDialog</item>
+        <item name="android:windowActionBar">false</item>
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowAnimationStyle">@null</item>
+    </style>
 </resources>
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/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt
index 0e39493..cfdcaff 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt
@@ -26,23 +26,10 @@
 
 /** Manager of [BackupRestoreStorage]. */
 class BackupRestoreStorageManager private constructor(private val application: Application) {
-    private val storages = ConcurrentHashMap<String, BackupRestoreStorage>()
+    private val storageWrappers = ConcurrentHashMap<String, StorageWrapper>()
 
     private val executor = MoreExecutors.directExecutor()
 
-    private val observer = Observer { reason -> notifyBackupManager(null, reason) }
-
-    private val keyedObserver =
-        KeyedObserver<Any?> { key, reason -> notifyBackupManager(key, reason) }
-
-    private fun notifyBackupManager(key: Any?, reason: Int) {
-        // prefer not triggering backup immediately after restore
-        if (reason == ChangeReason.RESTORE) return
-        // TODO: log storage name
-        Log.d(LOG_TAG, "Notify BackupManager data changed for change: key=$key")
-        BackupManager.dataChanged(application.packageName)
-    }
-
     /**
      * Adds all the registered [BackupRestoreStorage] as the helpers of given [BackupAgentHelper].
      *
@@ -52,7 +39,8 @@
      */
     fun addBackupAgentHelpers(backupAgentHelper: BackupAgentHelper) {
         val fileStorages = mutableListOf<BackupRestoreFileStorage>()
-        for ((keyPrefix, storage) in storages) {
+        for ((keyPrefix, storageWrapper) in storageWrappers) {
+            val storage = storageWrapper.storage
             if (storage is BackupRestoreFileStorage) {
                 fileStorages.add(storage)
             } else {
@@ -70,15 +58,8 @@
      * The observers of the storages will be notified.
      */
     fun onRestoreFinished() {
-        for (storage in storages.values) {
-            storage.notifyRestoreFinished()
-        }
-    }
-
-    private fun BackupRestoreStorage.notifyRestoreFinished() {
-        when (this) {
-            is KeyedObservable<*> -> notifyChange(ChangeReason.RESTORE)
-            is Observable -> notifyChange(ChangeReason.RESTORE)
+        for (storageWrapper in storageWrappers.values) {
+            storageWrapper.notifyRestoreFinished()
         }
     }
 
@@ -99,50 +80,82 @@
     fun add(storage: BackupRestoreStorage) {
         if (storage is BackupRestoreFileStorage) storage.checkFilePaths()
         val name = storage.name
-        val oldStorage = storages.put(name, storage)
+        val oldStorage = storageWrappers.put(name, StorageWrapper(storage))?.storage
         if (oldStorage != null) {
             throw IllegalStateException(
                 "Storage name '$name' conflicts:\n\told: $oldStorage\n\tnew: $storage"
             )
         }
-        storage.addObserver()
-    }
-
-    private fun BackupRestoreStorage.addObserver() {
-        when (this) {
-            is KeyedObservable<*> -> addObserver(keyedObserver, executor)
-            is Observable -> addObserver(observer, executor)
-            else ->
-                throw IllegalArgumentException(
-                    "$this does not implement either KeyedObservable or Observable"
-                )
-        }
     }
 
     /** Removes all the storages. */
     fun removeAll() {
-        for ((name, _) in storages) remove(name)
+        for ((name, _) in storageWrappers) remove(name)
     }
 
     /** Removes storage with given name. */
     fun remove(name: String): BackupRestoreStorage? {
-        val storage = storages.remove(name)
-        storage?.removeObserver()
-        return storage
-    }
-
-    private fun BackupRestoreStorage.removeObserver() {
-        when (this) {
-            is KeyedObservable<*> -> removeObserver(keyedObserver)
-            is Observable -> removeObserver(observer)
-        }
+        val storageWrapper = storageWrappers.remove(name)
+        storageWrapper?.removeObserver()
+        return storageWrapper?.storage
     }
 
     /** Returns storage with given name. */
-    fun get(name: String): BackupRestoreStorage? = storages[name]
+    fun get(name: String): BackupRestoreStorage? = storageWrappers[name]?.storage
 
     /** Returns storage with given name, exception is raised if not found. */
-    fun getOrThrow(name: String): BackupRestoreStorage = storages[name]!!
+    fun getOrThrow(name: String): BackupRestoreStorage = storageWrappers[name]!!.storage
+
+    private inner class StorageWrapper(val storage: BackupRestoreStorage) :
+        Observer, KeyedObserver<Any?> {
+        init {
+            when (storage) {
+                is KeyedObservable<*> -> storage.addObserver(this, executor)
+                is Observable -> storage.addObserver(this, executor)
+                else ->
+                    throw IllegalArgumentException(
+                        "$this does not implement either KeyedObservable or Observable"
+                    )
+            }
+        }
+
+        override fun onChanged(reason: Int) = onKeyChanged(null, reason)
+
+        override fun onKeyChanged(key: Any?, reason: Int) {
+            notifyBackupManager(key, reason)
+        }
+
+        private fun notifyBackupManager(key: Any?, reason: Int) {
+            val name = storage.name
+            // prefer not triggering backup immediately after restore
+            if (reason == ChangeReason.RESTORE) {
+                Log.d(
+                    LOG_TAG,
+                    "Notify BackupManager dataChanged ignored for restore: storage=$name key=$key"
+                )
+                return
+            }
+            Log.d(
+                LOG_TAG,
+                "Notify BackupManager dataChanged: storage=$name key=$key reason=$reason"
+            )
+            BackupManager.dataChanged(application.packageName)
+        }
+
+        fun removeObserver() {
+            when (storage) {
+                is KeyedObservable<*> -> storage.removeObserver(this)
+                is Observable -> storage.removeObserver(this)
+            }
+        }
+
+        fun notifyRestoreFinished() {
+            when (storage) {
+                is KeyedObservable<*> -> storage.notifyChange(ChangeReason.RESTORE)
+                is Observable -> storage.notifyChange(ChangeReason.RESTORE)
+            }
+        }
+    }
 
     companion object {
         @Volatile private var instance: BackupRestoreStorageManager? = null
diff --git a/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java b/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
index 18e8fc3..f47041d 100644
--- a/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
+++ b/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
@@ -85,7 +85,7 @@
      */
     @RequiresApi(Build.VERSION_CODES.M)
     public static void sendShowAdminSupportDetailsIntent(Context context, EnforcedAdmin admin) {
-        final Intent intent = getShowAdminSupportDetailsIntent(context, admin);
+        final Intent intent = getShowAdminSupportDetailsIntent(admin);
         int targetUserId = UserHandle.myUserId();
         if (admin != null) {
             if (admin.user != null
@@ -98,9 +98,16 @@
     }
 
     /**
-     * Gets the intent to trigger the {@code android.settings.ShowAdminSupportDetailsDialog}.
+     * @deprecated No context needed. Use {@link #getShowAdminSupportDetailsIntent(EnforcedAdmin)}.
      */
     public static Intent getShowAdminSupportDetailsIntent(Context context, EnforcedAdmin admin) {
+        return getShowAdminSupportDetailsIntent(admin);
+    }
+
+    /**
+     * Gets the intent to trigger the {@code android.settings.ShowAdminSupportDetailsDialog}.
+     */
+    public static Intent getShowAdminSupportDetailsIntent(EnforcedAdmin admin) {
         final Intent intent = new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS);
         if (admin != null) {
             if (admin.component != null) {
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenPathManager.kt
similarity index 69%
rename from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt
rename to packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenPathManager.kt
index f5fba7f..d590760 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenPathManager.kt
@@ -17,28 +17,25 @@
 package com.android.settingslib.spa.screenshot.util
 
 import androidx.test.platform.app.InstrumentationRegistry
-import platform.test.screenshot.GoldenImagePathManager
+import platform.test.screenshot.GoldenPathManager
 import platform.test.screenshot.PathConfig
 
-/** A [GoldenImagePathManager] that should be used for all Settings screenshot tests. */
-class SettingsGoldenImagePathManager(
-    pathConfig: PathConfig,
-    assetsPathRelativeToBuildRoot: String
-) :
-    GoldenImagePathManager(
+/** A [GoldenPathManager] that should be used for all Settings screenshot tests. */
+class SettingsGoldenPathManager(pathConfig: PathConfig, assetsPathRelativeToBuildRoot: String) :
+    GoldenPathManager(
         appContext = InstrumentationRegistry.getInstrumentation().context,
         assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
         deviceLocalPath =
-        InstrumentationRegistry.getInstrumentation()
-            .targetContext
-            .filesDir
-            .absolutePath
-            .toString() + "/settings_screenshots",
+            InstrumentationRegistry.getInstrumentation()
+                .targetContext
+                .filesDir
+                .absolutePath
+                .toString() + "/settings_screenshots",
         pathConfig = pathConfig,
     ) {
     override fun toString(): String {
         // This string is appended to all actual/expected screenshots on the device, so make sure
         // it is a static value.
-        return "SettingsGoldenImagePathManager"
+        return "SettingsGoldenPathManager"
     }
 }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
index ae85675..16f6b5e 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
@@ -44,7 +44,7 @@
     private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
     private val screenshotRule =
         ScreenshotTestRule(
-            SettingsGoldenImagePathManager(
+            SettingsGoldenPathManager(
                 getEmulatedDevicePathConfig(emulationSpec),
                 assetsPathRelativeToBuildRoot
             )
diff --git a/packages/SettingsLib/res/layout/dialog_with_icon.xml b/packages/SettingsLib/res/layout/dialog_with_icon.xml
index 3586dcb..b21895b 100644
--- a/packages/SettingsLib/res/layout/dialog_with_icon.xml
+++ b/packages/SettingsLib/res/layout/dialog_with_icon.xml
@@ -35,12 +35,14 @@
             android:id="@+id/dialog_with_icon_title"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:hyphenationFrequency="fullFast"
             android:gravity="center"
             style="@style/DialogWithIconTitle"/>
         <TextView
             android:id="@+id/dialog_with_icon_message"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:hyphenationFrequency="fullFast"
             android:gravity="center"
             style="@style/TextAppearanceSmall"/>
 
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/mobile/MobileMappings.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
index 840c936..b7108c9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
@@ -236,7 +236,8 @@
             // Handle specific carrier config values for the default data SIM
             int defaultDataSubId = SubscriptionManager.from(context)
                     .getDefaultDataSubscriptionId();
-            PersistableBundle b = configMgr.getConfigForSubId(defaultDataSubId);
+            PersistableBundle b = configMgr == null ? null
+                        : configMgr.getConfigForSubId(defaultDataSubId);
             if (b != null) {
                 config.alwaysShowDataRatIcon = b.getBoolean(
                         CarrierConfigManager.KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL);
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
index 53daef1..69c7410 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
@@ -242,6 +242,7 @@
                         .setMessage(messageResId)
                         .setNegativeButtonText(R.string.cancel)
                         .setPositiveButtonText(R.string.next);
+                mCustomDialogHelper.requestFocusOnTitle();
                 break;
             case GRANT_ADMIN_DIALOG:
                 mEditUserInfoView.setVisibility(View.GONE);
@@ -254,6 +255,7 @@
                         .setMessage(R.string.user_grant_admin_message)
                         .setNegativeButtonText(R.string.back)
                         .setPositiveButtonText(R.string.next);
+                mCustomDialogHelper.requestFocusOnTitle();
                 if (mIsAdmin == null) {
                     mCustomDialogHelper.setButtonEnabled(false);
                 }
@@ -265,6 +267,7 @@
                         .setTitle(R.string.user_info_settings_title)
                         .setNegativeButtonText(R.string.back)
                         .setPositiveButtonText(R.string.done);
+                mCustomDialogHelper.requestFocusOnTitle();
                 mEditUserInfoView.setVisibility(View.VISIBLE);
                 mGrantAdminView.setVisibility(View.GONE);
                 break;
@@ -273,7 +276,6 @@
                         && mEditUserPhotoController.getNewUserPhotoDrawable() != null)
                         ? mEditUserPhotoController.getNewUserPhotoDrawable()
                         : mSavedDrawable;
-
                 String newName = mUserNameView.getText().toString().trim();
                 String defaultName = mActivity.getString(R.string.user_new_user_name);
                 mUserName = !newName.isEmpty() ? newName : defaultName;
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java b/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java
index 5201b3d..4cf3bc2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java
@@ -23,6 +23,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
 import android.widget.Button;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -282,4 +283,13 @@
         }
         return this;
     }
+
+    /**
+     * Requests focus on dialog title when used. Used to let talkback know that the dialog content
+     * is updated and needs to be read from the beginning.
+     */
+    public void requestFocusOnTitle() {
+        mDialogTitle.requestFocus();
+        mDialogTitle.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+    }
 }
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 56b0bf7..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
@@ -23,8 +23,8 @@
 import com.android.settingslib.volume.shared.model.AudioStreamModel
 import com.android.settingslib.volume.shared.model.RingerMode
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 
 /** Provides audio stream state and an ability to change it */
@@ -43,6 +43,9 @@
             streamModel.copy(volume = processVolume(streamModel, ringerMode, isZenMuted))
         }
 
+    val ringerMode: StateFlow<RingerMode>
+        get() = audioRepository.ringerMode
+
     suspend fun setVolume(audioStream: AudioStream, volume: Int) =
         audioRepository.setVolume(audioStream, volume)
 
@@ -52,9 +55,14 @@
     /** Checks if the volume can be changed via the UI. */
     fun canChangeVolume(audioStream: AudioStream): Flow<Boolean> {
         return if (audioStream.value == AudioManager.STREAM_NOTIFICATION) {
-            getAudioStream(AudioStream(AudioManager.STREAM_RING)).map { !it.isMuted }
+            combine(
+                notificationsSoundPolicyInteractor.isZenMuted(audioStream),
+                getAudioStream(AudioStream(AudioManager.STREAM_RING)).map { it.isMuted },
+            ) { isZenMuted, isRingMuted ->
+                !isZenMuted && !isRingMuted
+            }
         } else {
-            flowOf(true)
+            notificationsSoundPolicyInteractor.isZenMuted(audioStream).map { !it }
         }
     }
 
@@ -76,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/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
index fe1529d..9c518de 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
@@ -192,7 +192,7 @@
         mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
 
         final Executor executor = (command -> new Thread(command).start());
-        final BluetoothVolumeControl.Callback callback = (device, volumeOffset) -> {};
+        final BluetoothVolumeControl.Callback callback = new BluetoothVolumeControl.Callback() {};
         mProfile.registerCallback(executor, callback);
 
         verify(mService).registerCallback(executor, callback);
@@ -200,7 +200,7 @@
 
     @Test
     public void unregisterCallback_verifyIsCalled() {
-        final BluetoothVolumeControl.Callback callback = (device, volumeOffset) -> {};
+        final BluetoothVolumeControl.Callback callback = new BluetoothVolumeControl.Callback() {};
         mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
 
         mProfile.unregisterCallback(callback);
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 30d5d4b..eaec617 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -269,6 +269,7 @@
         Settings.Secure.STYLUS_POINTER_ICON_ENABLED,
         Settings.Secure.CAMERA_EXTENSIONS_FALLBACK,
         Settings.Secure.VISUAL_QUERY_ACCESSIBILITY_DETECTION_ENABLED,
-        Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS
+        Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
+        Settings.Secure.AUDIO_DEVICE_INVENTORY
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 893932f..046d6e2 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -424,5 +424,6 @@
         VALIDATORS.put(Secure.STYLUS_POINTER_ICON_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.CAMERA_EXTENSIONS_FALLBACK, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.IMMERSIVE_MODE_CONFIRMATIONS, ANY_STRING_VALIDATOR);
+        VALIDATORS.put(Secure.AUDIO_DEVICE_INVENTORY, ANY_STRING_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 46c89900..6eb2dd0 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -670,7 +670,6 @@
                  Settings.Secure.AUTOMATIC_STORAGE_MANAGER_ENABLED,
                  Settings.Secure.AUTOMATIC_STORAGE_MANAGER_LAST_RUN,
                  Settings.Secure.AUTOMATIC_STORAGE_MANAGER_TURNED_OFF_BY_POLICY,
-                 Settings.Secure.AUDIO_DEVICE_INVENTORY, // not controllable by user
                  Settings.Secure.AUDIO_SAFE_CSD_AS_A_FEATURE_ENABLED, // not controllable by user
                  Settings.Secure.BACKUP_AUTO_RESTORE,
                  Settings.Secure.BACKUP_ENABLED,
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 98591e9..3c18f17 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -179,6 +179,7 @@
     <uses-permission android:name="android.permission.RECORD_AUDIO" />
     <uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT"/>
     <uses-permission android:name="android.permission.USE_EXACT_ALARM"/>
+    <uses-permission android:name="android.permission.RECORD_SENSITIVE_CONTENT"/>
 
     <!-- Assist -->
     <uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" />
@@ -910,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 da06830..1c98630 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -115,10 +115,10 @@
 }
 
 flag {
-    name: "notifications_background_media_icons"
+    name: "notifications_background_icons"
     namespace: "systemui"
-    description: "Updates icons for media notifications in the background."
-    bug: "315143160"
+    description: "Moves part of the notification icon updates to the background."
+    bug: "315143361"
     metadata {
         purpose: PURPOSE_BUGFIX
     }
@@ -463,6 +463,13 @@
 }
 
 flag {
+   name: "enable_contextual_tips_frequency_cap"
+   description: "Enables frequency capping for contextual tips, e.g. 1x/day, 2x/week, 3x/lifetime."
+   namespace: "systemui"
+   bug: "322891421"
+}
+
+flag {
    name: "enable_contextual_tips"
    description: "Enables showing contextual tips."
    namespace: "systemui"
@@ -617,3 +624,14 @@
     description: "enables new focus outline for qs tiles when focused on with physical keyboard"
     bug: "312899524"
 }
+
+flag {
+   name: "edgeback_gesture_handler_get_running_tasks_background"
+    namespace: "systemui"
+    description: "Decide whether to get the running tasks from activity manager in EdgebackGestureHandler"
+        " class on the background thread."
+    bug: "325041960"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
index 5d5f12e..3f57f88 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
@@ -337,6 +337,7 @@
         if (ghostedView is LaunchableView) {
             // Restore the ghosted view visibility.
             ghostedView.setShouldBlockVisibilityChanges(false)
+            ghostedView.onActivityLaunchAnimationEnd()
         } else {
             // Make the ghosted view visible. We ensure that the view is considered VISIBLE by
             // accessibility by first making it INVISIBLE then VISIBLE (see b/204944038#comment17
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
index ed8e705..da6ccaa 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
@@ -38,6 +38,9 @@
      * @param block whether we should block/postpone all calls to `setVisibility`.
      */
     fun setShouldBlockVisibilityChanges(block: Boolean)
+
+    /** Perform an action when the activity launch animation ends */
+    fun onActivityLaunchAnimationEnd() {}
 }
 
 /** A delegate that can be used by views to make the implementation of [LaunchableView] easier. */
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 ef15c84..9a99649 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
@@ -21,22 +21,25 @@
 import androidx.compose.animation.core.animateDpAsState
 import androidx.compose.animation.core.animateFloatAsState
 import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.background
 import androidx.compose.foundation.interaction.DragInteraction
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.isSystemInDarkTheme
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.aspectRatio
-import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.LocalContentColor
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Slider
 import androidx.compose.material3.SliderState
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -44,17 +47,28 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
 import androidx.compose.ui.geometry.CornerRadius
 import androidx.compose.ui.geometry.RoundRect
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Path
 import androidx.compose.ui.graphics.drawscope.clipPath
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasurePolicy
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.layout.layoutId
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
-import com.android.compose.modifiers.padding
+import androidx.compose.ui.util.fastFirst
+import androidx.compose.ui.util.fastFirstOrNull
 import com.android.compose.theme.LocalAndroidColorScheme
 
 /**
@@ -62,15 +76,16 @@
  *
  * @param onValueChangeFinished is called when the slider settles on a [value]. This callback
  *   shouldn't be used to react to value changes. Use [onValueChange] instead
- * @param interactionSource - the [MutableInteractionSource] representing the stream of Interactions
+ * @param interactionSource the [MutableInteractionSource] representing the stream of Interactions
  *   for this slider. You can create and pass in your own remembered instance to observe
  *   Interactions and customize the appearance / behavior of this slider in different states.
- * @param colors - slider color scheme.
- * @param draggingCornersRadius - radius of the slider indicator when the user drags it
- * @param icon - icon at the start of the slider. Icon is limited to a square space at the start of
- *   the slider
- * @param label - control shown next to the icon.
+ * @param colors determine slider color scheme.
+ * @param draggingCornersRadius is the radius of the slider indicator when the user drags it
+ * @param icon at the start of the slider. Icon is limited to a square space at the start of the
+ *   slider
+ * @param label is shown next to the icon.
  */
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun PlatformSlider(
     value: Float,
@@ -86,7 +101,7 @@
     label: (@Composable (isDragging: Boolean) -> Unit)? = null,
 ) {
     val sliderHeight: Dp = 64.dp
-    val iconWidth: Dp = sliderHeight
+    val thumbSize: Dp = sliderHeight
     var isDragging by remember { mutableStateOf(false) }
     LaunchedEffect(interactionSource) {
         interactionSource.interactions.collect { interaction ->
@@ -101,16 +116,6 @@
             }
         }
     }
-    val paddingStart by
-        animateDpAsState(
-            targetValue =
-                if ((!isDragging && value == valueRange.start) || icon == null) {
-                    16.dp
-                } else {
-                    0.dp
-                },
-            label = "LabelIconSpacingAnimation"
-        )
 
     Box(modifier = modifier.height(sliderHeight)) {
         Slider(
@@ -126,130 +131,275 @@
                     sliderState = it,
                     enabled = enabled,
                     colors = colors,
-                    iconWidth = iconWidth,
                     draggingCornersRadius = draggingCornersRadius,
                     sliderHeight = sliderHeight,
+                    thumbSize = thumbSize,
                     isDragging = isDragging,
-                    modifier = Modifier,
+                    label = label,
+                    icon = icon,
+                    modifier = Modifier.fillMaxSize(),
                 )
             },
-            thumb = { Spacer(Modifier.width(iconWidth).height(sliderHeight)) },
+            thumb = { Spacer(Modifier.size(thumbSize)) },
         )
 
-        if (icon != null || label != null) {
-            Row(modifier = Modifier.fillMaxSize()) {
-                icon?.let { iconComposable ->
-                    Box(
-                        modifier = Modifier.fillMaxHeight().aspectRatio(1f),
-                        contentAlignment = Alignment.Center,
-                    ) {
-                        iconComposable(isDragging)
-                    }
-                }
-
-                label?.let { labelComposable ->
-                    Box(
-                        modifier =
-                            Modifier.fillMaxHeight()
-                                .weight(1f)
-                                .padding(
-                                    start = { paddingStart.roundToPx() },
-                                    end = { sliderHeight.roundToPx() / 2 },
-                                ),
-                        contentAlignment = Alignment.CenterStart,
-                    ) {
-                        labelComposable(isDragging)
-                    }
-                }
-            }
-        }
+        Spacer(
+            Modifier.padding(8.dp)
+                .size(4.dp)
+                .align(Alignment.CenterEnd)
+                .background(color = colors.indicatorColor, shape = CircleShape)
+        )
     }
 }
 
+private enum class TrackComponent(val zIndex: Float) {
+    Background(0f),
+    Icon(1f),
+    Label(1f),
+}
+
 @Composable
 private fun Track(
     sliderState: SliderState,
     enabled: Boolean,
     colors: PlatformSliderColors,
-    iconWidth: Dp,
     draggingCornersRadius: Dp,
     sliderHeight: Dp,
+    thumbSize: Dp,
     isDragging: Boolean,
+    icon: (@Composable (isDragging: Boolean) -> Unit)?,
+    label: (@Composable (isDragging: Boolean) -> Unit)?,
     modifier: Modifier = Modifier,
 ) {
     val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
-    val iconWidthPx: Float
-    val halfIconWidthPx: Float
-    val targetIndicatorRadiusPx: Float
-    val halfSliderHeightPx: Float
-    with(LocalDensity.current) {
-        halfSliderHeightPx = sliderHeight.toPx() / 2
-        iconWidthPx = iconWidth.toPx()
-        halfIconWidthPx = iconWidthPx / 2
-        targetIndicatorRadiusPx =
-            if (isDragging) draggingCornersRadius.toPx() else halfSliderHeightPx
-    }
+    var drawingState: DrawingState by remember { mutableStateOf(DrawingState()) }
+    Layout(
+        modifier = modifier,
+        content = {
+            TrackBackground(
+                modifier = Modifier.layoutId(TrackComponent.Background),
+                drawingState = drawingState,
+                enabled = enabled,
+                colors = colors,
+                draggingCornersRadiusActive = draggingCornersRadius,
+                draggingCornersRadiusIdle = sliderHeight / 2,
+                isDragging = isDragging,
+            )
+            if (icon != null) {
+                Box(
+                    modifier = Modifier.layoutId(TrackComponent.Icon).clip(CircleShape),
+                    contentAlignment = Alignment.Center,
+                ) {
+                    CompositionLocalProvider(
+                        LocalContentColor provides
+                            if (enabled) colors.iconColor else colors.disabledIconColor
+                    ) {
+                        icon(isDragging)
+                    }
+                }
+            }
+            if (label != null) {
+                val offsetX by
+                    animateFloatAsState(
+                        targetValue =
+                            if (enabled) {
+                                if (drawingState.isLabelOnTopOfIndicator) {
+                                    drawingState.iconWidth.coerceAtLeast(
+                                        LocalDensity.current.run { 16.dp.toPx() }
+                                    )
+                                } else {
+                                    val indicatorWidth =
+                                        drawingState.indicatorRight - drawingState.indicatorLeft
+                                    indicatorWidth + LocalDensity.current.run { 16.dp.toPx() }
+                                }
+                            } else {
+                                drawingState.iconWidth
+                            },
+                        label = "LabelIconSpacingAnimation"
+                    )
+                Box(
+                    modifier =
+                        Modifier.layoutId(TrackComponent.Label).offset {
+                            IntOffset(offsetX.toInt(), 0)
+                        },
+                    contentAlignment = Alignment.CenterStart,
+                ) {
+                    CompositionLocalProvider(
+                        LocalContentColor provides
+                            colors.getLabelColor(
+                                isEnabled = enabled,
+                                isLabelOnTopOfTheIndicator = drawingState.isLabelOnTopOfIndicator,
+                            )
+                    ) {
+                        label(isDragging)
+                    }
+                }
+            }
+        },
+        measurePolicy =
+            TrackMeasurePolicy(
+                sliderState = sliderState,
+                thumbSize = LocalDensity.current.run { thumbSize.roundToPx() },
+                isRtl = isRtl,
+                onDrawingStateMeasured = { drawingState = it }
+            )
+    )
+}
 
-    val indicatorRadiusPx: Float by
-        animateFloatAsState(
-            targetValue = targetIndicatorRadiusPx,
+@Composable
+private fun TrackBackground(
+    drawingState: DrawingState,
+    enabled: Boolean,
+    colors: PlatformSliderColors,
+    draggingCornersRadiusActive: Dp,
+    draggingCornersRadiusIdle: Dp,
+    isDragging: Boolean,
+    modifier: Modifier = Modifier,
+) {
+    val indicatorRadiusDp: Dp by
+        animateDpAsState(
+            targetValue =
+                if (isDragging) draggingCornersRadiusActive else draggingCornersRadiusIdle,
             label = "PlatformSliderCornersAnimation",
         )
 
     val trackColor = colors.getTrackColor(enabled)
     val indicatorColor = colors.getIndicatorColor(enabled)
-    val trackCornerRadius = CornerRadius(halfSliderHeightPx, halfSliderHeightPx)
-    val indicatorCornerRadius = CornerRadius(indicatorRadiusPx, indicatorRadiusPx)
     Canvas(modifier.fillMaxSize()) {
+        val trackCornerRadius = CornerRadius(size.height / 2, size.height / 2)
         val trackPath = Path()
         trackPath.addRoundRect(
             RoundRect(
-                left = -halfIconWidthPx,
+                left = 0f,
                 top = 0f,
-                right = size.width + halfIconWidthPx,
-                bottom = size.height,
+                right = drawingState.totalWidth,
+                bottom = drawingState.totalHeight,
                 cornerRadius = trackCornerRadius,
             )
         )
         drawPath(path = trackPath, color = trackColor)
 
+        val indicatorCornerRadius = CornerRadius(indicatorRadiusDp.toPx(), indicatorRadiusDp.toPx())
         clipPath(trackPath) {
             val indicatorPath = Path()
-            if (isRtl) {
-                indicatorPath.addRoundRect(
-                    RoundRect(
-                        left =
-                            size.width -
-                                size.width * sliderState.coercedNormalizedValue -
-                                halfIconWidthPx,
-                        top = 0f,
-                        right = size.width + iconWidthPx,
-                        bottom = size.height,
-                        topLeftCornerRadius = indicatorCornerRadius,
-                        topRightCornerRadius = trackCornerRadius,
-                        bottomRightCornerRadius = trackCornerRadius,
-                        bottomLeftCornerRadius = indicatorCornerRadius,
-                    )
+            indicatorPath.addRoundRect(
+                RoundRect(
+                    left = drawingState.indicatorLeft,
+                    top = drawingState.indicatorTop,
+                    right = drawingState.indicatorRight,
+                    bottom = drawingState.indicatorBottom,
+                    topLeftCornerRadius = trackCornerRadius,
+                    topRightCornerRadius = indicatorCornerRadius,
+                    bottomRightCornerRadius = indicatorCornerRadius,
+                    bottomLeftCornerRadius = trackCornerRadius,
                 )
-            } else {
-                indicatorPath.addRoundRect(
-                    RoundRect(
-                        left = -halfIconWidthPx,
-                        top = 0f,
-                        right = size.width * sliderState.coercedNormalizedValue + halfIconWidthPx,
-                        bottom = size.height,
-                        topLeftCornerRadius = trackCornerRadius,
-                        topRightCornerRadius = indicatorCornerRadius,
-                        bottomRightCornerRadius = indicatorCornerRadius,
-                        bottomLeftCornerRadius = trackCornerRadius,
-                    )
-                )
-            }
+            )
             drawPath(path = indicatorPath, color = indicatorColor)
         }
     }
 }
 
+/** Measures track components sizes and calls [onDrawingStateMeasured] when it's done. */
+private class TrackMeasurePolicy(
+    private val sliderState: SliderState,
+    private val thumbSize: Int,
+    private val isRtl: Boolean,
+    private val onDrawingStateMeasured: (DrawingState) -> Unit,
+) : MeasurePolicy {
+
+    override fun MeasureScope.measure(
+        measurables: List<Measurable>,
+        constraints: Constraints
+    ): MeasureResult {
+        // Slider adds a paddings to the Track to make spase for thumb
+        val desiredWidth = constraints.maxWidth + thumbSize
+        val desiredHeight = constraints.maxHeight
+        val backgroundPlaceable: Placeable =
+            measurables
+                .fastFirst { it.layoutId == TrackComponent.Background }
+                .measure(Constraints(desiredWidth, desiredWidth, desiredHeight, desiredHeight))
+
+        val iconPlaceable: Placeable? =
+            measurables
+                .fastFirstOrNull { it.layoutId == TrackComponent.Icon }
+                ?.measure(
+                    Constraints(
+                        minWidth = desiredHeight,
+                        maxWidth = desiredHeight,
+                        minHeight = desiredHeight,
+                        maxHeight = desiredHeight,
+                    )
+                )
+
+        val iconSize = iconPlaceable?.width ?: 0
+        val labelMaxWidth = (desiredWidth - iconSize) / 2
+        val labelPlaceable: Placeable? =
+            measurables
+                .fastFirstOrNull { it.layoutId == TrackComponent.Label }
+                ?.measure(
+                    Constraints(
+                        minWidth = 0,
+                        maxWidth = labelMaxWidth,
+                        minHeight = desiredHeight,
+                        maxHeight = desiredHeight,
+                    )
+                )
+
+        val drawingState =
+            if (isRtl) {
+                DrawingState(
+                    isRtl = true,
+                    totalWidth = desiredWidth.toFloat(),
+                    totalHeight = desiredHeight.toFloat(),
+                    indicatorLeft =
+                        (desiredWidth - iconSize) * (1 - sliderState.coercedNormalizedValue),
+                    indicatorTop = 0f,
+                    indicatorRight = desiredWidth.toFloat(),
+                    indicatorBottom = desiredHeight.toFloat(),
+                    iconWidth = iconSize.toFloat(),
+                    labelWidth = labelPlaceable?.width?.toFloat() ?: 0f,
+                )
+            } else {
+                DrawingState(
+                    isRtl = false,
+                    totalWidth = desiredWidth.toFloat(),
+                    totalHeight = desiredHeight.toFloat(),
+                    indicatorLeft = 0f,
+                    indicatorTop = 0f,
+                    indicatorRight =
+                        iconSize + (desiredWidth - iconSize) * sliderState.coercedNormalizedValue,
+                    indicatorBottom = desiredHeight.toFloat(),
+                    iconWidth = iconSize.toFloat(),
+                    labelWidth = labelPlaceable?.width?.toFloat() ?: 0f,
+                )
+            }
+
+        onDrawingStateMeasured(drawingState)
+
+        return layout(desiredWidth, desiredHeight) {
+            backgroundPlaceable.placeRelative(0, 0, TrackComponent.Background.zIndex)
+
+            iconPlaceable?.placeRelative(0, 0, TrackComponent.Icon.zIndex)
+            labelPlaceable?.placeRelative(0, 0, TrackComponent.Label.zIndex)
+        }
+    }
+}
+
+private data class DrawingState(
+    val isRtl: Boolean = false,
+    val totalWidth: Float = 0f,
+    val totalHeight: Float = 0f,
+    val indicatorLeft: Float = 0f,
+    val indicatorTop: Float = 0f,
+    val indicatorRight: Float = 0f,
+    val indicatorBottom: Float = 0f,
+    val iconWidth: Float = 0f,
+    val labelWidth: Float = 0f,
+)
+
+private val DrawingState.isLabelOnTopOfIndicator: Boolean
+    get() = labelWidth < indicatorRight - indicatorLeft - iconWidth
+
 /** [SliderState.value] normalized using [SliderState.valueRange]. The result belongs to [0, 1] */
 private val SliderState.coercedNormalizedValue: Float
     get() {
@@ -268,17 +418,19 @@
  * @param trackColor fills the track of the slider. This is a "background" of the slider
  * @param indicatorColor fills the slider from the start to the value
  * @param iconColor is the default icon color
- * @param labelColor is the default icon color
+ * @param labelColorOnIndicator is the label color for when it's shown on top of the indicator
+ * @param labelColorOnTrack is the label color for when it's shown on top of the track
  * @param disabledTrackColor is the [trackColor] when the PlatformSlider#enabled == false
  * @param disabledIndicatorColor is the [indicatorColor] when the PlatformSlider#enabled == false
  * @param disabledIconColor is the [iconColor] when the PlatformSlider#enabled == false
- * @param disabledLabelColor is the [labelColor] when the PlatformSlider#enabled == false
+ * @param disabledLabelColor is the label color when the PlatformSlider#enabled == false
  */
 data class PlatformSliderColors(
     val trackColor: Color,
     val indicatorColor: Color,
     val iconColor: Color,
-    val labelColor: Color,
+    val labelColorOnIndicator: Color,
+    val labelColorOnTrack: Color,
     val disabledTrackColor: Color,
     val disabledIndicatorColor: Color,
     val disabledIconColor: Color,
@@ -300,10 +452,11 @@
 @Composable
 private fun lightThemePlatformSliderColors() =
     PlatformSliderColors(
-        trackColor = MaterialTheme.colorScheme.tertiaryContainer,
-        indicatorColor = LocalAndroidColorScheme.current.tertiaryFixedDim,
-        iconColor = MaterialTheme.colorScheme.onTertiaryContainer,
-        labelColor = MaterialTheme.colorScheme.onTertiaryContainer,
+        trackColor = LocalAndroidColorScheme.current.tertiaryFixedDim,
+        indicatorColor = MaterialTheme.colorScheme.tertiary,
+        iconColor = MaterialTheme.colorScheme.onTertiary,
+        labelColorOnIndicator = MaterialTheme.colorScheme.onTertiary,
+        labelColorOnTrack = LocalAndroidColorScheme.current.onTertiaryFixed,
         disabledTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest,
         disabledIndicatorColor = MaterialTheme.colorScheme.surfaceContainerHighest,
         disabledIconColor = MaterialTheme.colorScheme.outline,
@@ -314,10 +467,11 @@
 @Composable
 private fun darkThemePlatformSliderColors() =
     PlatformSliderColors(
-        trackColor = MaterialTheme.colorScheme.onTertiary,
-        indicatorColor = LocalAndroidColorScheme.current.onTertiaryFixedVariant,
+        trackColor = MaterialTheme.colorScheme.tertiary,
+        indicatorColor = MaterialTheme.colorScheme.tertiary,
         iconColor = MaterialTheme.colorScheme.onTertiaryContainer,
-        labelColor = MaterialTheme.colorScheme.onTertiaryContainer,
+        labelColorOnIndicator = MaterialTheme.colorScheme.onTertiary,
+        labelColorOnTrack = LocalAndroidColorScheme.current.onTertiaryFixed,
         disabledTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest,
         disabledIndicatorColor = MaterialTheme.colorScheme.surfaceContainerHighest,
         disabledIconColor = MaterialTheme.colorScheme.outline,
@@ -329,3 +483,14 @@
 
 private fun PlatformSliderColors.getIndicatorColor(isEnabled: Boolean): Color =
     if (isEnabled) indicatorColor else disabledIndicatorColor
+
+private fun PlatformSliderColors.getLabelColor(
+    isEnabled: Boolean,
+    isLabelOnTopOfTheIndicator: Boolean
+): Color {
+    return if (isEnabled) {
+        if (isLabelOnTopOfTheIndicator) labelColorOnIndicator else labelColorOnTrack
+    } else {
+        disabledLabelColor
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index a3372e3..d0c4984 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -24,7 +24,6 @@
 import com.android.compose.animation.scene.updateSceneTransitionLayoutState
 import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.communal.ui.compose.extensions.allowGestures
 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.res.R
@@ -84,7 +83,7 @@
 
     SceneTransitionLayout(
         state = sceneTransitionLayoutState,
-        modifier = modifier.fillMaxSize().allowGestures(allowed = touchesAllowed),
+        modifier = modifier.fillMaxSize(),
         swipeSourceDetector =
             FixedSizeEdgeDetector(
                 dimensionResource(id = R.dimen.communal_gesture_initiation_width)
@@ -93,9 +92,14 @@
         scene(
             CommunalScenes.Blank,
             userActions =
-                mapOf(
-                    Swipe(SwipeDirection.Left, fromSource = Edge.Right) to CommunalScenes.Communal
-                )
+                if (touchesAllowed) {
+                    mapOf(
+                        Swipe(SwipeDirection.Left, fromSource = Edge.Right) to
+                            CommunalScenes.Communal
+                    )
+                } else {
+                    emptyMap()
+                }
         ) {
             // This scene shows nothing only allowing for transitions to the communal scene.
             Box(modifier = Modifier.fillMaxSize())
@@ -104,7 +108,13 @@
         scene(
             CommunalScenes.Communal,
             userActions =
-                mapOf(Swipe(SwipeDirection.Right, fromSource = Edge.Left) to CommunalScenes.Blank),
+                if (touchesAllowed) {
+                    mapOf(
+                        Swipe(SwipeDirection.Right, fromSource = Edge.Left) to CommunalScenes.Blank
+                    )
+                } else {
+                    emptyMap()
+                },
         ) {
             CommunalScene(viewModel, dialogFactory, modifier = modifier)
         }
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/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index 96520b2..7acb4d5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -19,61 +19,31 @@
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
-import com.android.compose.animation.scene.Edge
-import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.SceneScope
-import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.compose.animation.scene.animateSceneFloatAsState
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
 import com.android.systemui.qs.ui.composable.QuickSettings
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.ui.composable.ComposableScene
 import dagger.Lazy
 import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.stateIn
 
 /** The lock screen scene shows when the device is locked. */
 @SysUISingleton
 class LockscreenScene
 @Inject
 constructor(
-    @Application private val applicationScope: CoroutineScope,
     viewModel: LockscreenSceneViewModel,
     private val lockscreenContent: Lazy<LockscreenContent>,
 ) : ComposableScene {
     override val key = Scenes.Lockscreen
 
     override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
-        combine(
-                viewModel.upDestinationSceneKey,
-                viewModel.leftDestinationSceneKey,
-                viewModel.downFromTopEdgeDestinationSceneKey,
-            ) { upKey, leftKey, downFromTopEdgeKey ->
-                destinationScenes(
-                    up = upKey,
-                    left = leftKey,
-                    downFromTopEdge = downFromTopEdgeKey,
-                )
-            }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.Eagerly,
-                initialValue =
-                    destinationScenes(
-                        up = viewModel.upDestinationSceneKey.value,
-                        left = viewModel.leftDestinationSceneKey.value,
-                        downFromTopEdge = viewModel.downFromTopEdgeDestinationSceneKey.value,
-                    )
-            )
+        viewModel.destinationScenes
 
     @Composable
     override fun SceneScope.Content(
@@ -84,22 +54,6 @@
             modifier = modifier,
         )
     }
-
-    private fun destinationScenes(
-        up: SceneKey?,
-        left: SceneKey?,
-        downFromTopEdge: SceneKey?,
-    ): Map<UserAction, UserActionResult> {
-        return buildMap {
-            up?.let { this[Swipe(SwipeDirection.Up)] = UserActionResult(up) }
-            left?.let { this[Swipe(SwipeDirection.Left)] = UserActionResult(left) }
-            downFromTopEdge?.let {
-                this[Swipe(fromSource = Edge.Top, direction = SwipeDirection.Down)] =
-                    UserActionResult(downFromTopEdge)
-            }
-            this[Swipe(direction = SwipeDirection.Down)] = UserActionResult(Scenes.Shade)
-        }
-    }
 }
 
 @Composable
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 51464d0..31201c2f 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
@@ -373,7 +373,6 @@
 ) {
     if (viewModel.isMediaVisible()) {
         val density = LocalDensity.current
-        val layoutWidth = remember { mutableStateOf(0) }
         val mediaHeight = dimensionResource(R.dimen.qs_media_session_height_expanded)
 
         MediaCarousel(
@@ -389,7 +388,7 @@
                     layout(placeable.width, placeable.height) { placeable.placeRelative(0, 0) }
                 },
             mediaHost = mediaHost,
-            layoutWidth = layoutWidth.value,
+            layoutWidth = 0,
             layoutHeight = with(density) { mediaHeight.toPx() }.toInt(),
             carouselController = mediaCarouselController,
         )
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
index 8ac84ff..b1fbe35 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.volume.panel.component.anc.ui.composable
 
 import android.content.Context
+import android.view.ContextThemeWrapper
 import android.view.View
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.material3.MaterialTheme
@@ -73,15 +74,16 @@
         AndroidView<SliceView>(
             modifier = Modifier.fillMaxWidth(),
             factory = { context: Context ->
-                SliceView(context).apply {
-                    mode = SliceView.MODE_LARGE
-                    isScrollable = false
-                    importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
-                    setShowTitleItems(true)
-                    addOnLayoutChangeListener(
-                        OnWidthChangedLayoutListener(viewModel::changeSliceWidth)
-                    )
-                }
+                SliceView(ContextThemeWrapper(context, R.style.Widget_SliceView_VolumePanel))
+                    .apply {
+                        mode = SliceView.MODE_LARGE
+                        isScrollable = false
+                        importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
+                        setShowTitleItems(true)
+                        addOnLayoutChangeListener(
+                            OnWidthChangedLayoutListener(viewModel::changeSliceWidth)
+                        )
+                    }
             },
             update = { sliceView: SliceView -> sliceView.slice = slice }
         )
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 4d810df..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
@@ -42,12 +42,14 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
 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
@@ -61,12 +63,13 @@
 @Composable
 fun ColumnVolumeSliders(
     viewModels: List<SliderViewModel>,
+    isExpanded: Boolean,
+    onExpandedChanged: (Boolean) -> Unit,
     sliderColors: PlatformSliderColors,
     isExpandable: Boolean,
     modifier: Modifier = Modifier,
 ) {
     require(viewModels.isNotEmpty())
-    var isExpanded: Boolean by remember(isExpandable) { mutableStateOf(!isExpandable) }
     val transition = updateTransition(isExpanded, label = "CollapsableSliders")
     Column(modifier = modifier) {
         Row(
@@ -78,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 = { isExpanded = it },
-                    sliderColors,
-                    Modifier,
+                    onExpandedChanged = onExpandedChanged,
+                    sliderColors = sliderColors,
                 )
             }
         }
@@ -116,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 18a62dc..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,18 +18,28 @@
 
 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
 import com.android.systemui.common.ui.compose.Icon
@@ -38,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(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,
@@ -64,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/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt
index 1ca18de..fdf8ee8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt
@@ -48,8 +48,11 @@
                 modifier = modifier.fillMaxWidth(),
             )
         } else {
+            val isExpanded by viewModel.isExpanded.collectAsState()
             ColumnVolumeSliders(
                 viewModels = sliderViewModels,
+                isExpanded = isExpanded,
+                onExpandedChanged = viewModel::onExpandedChanged,
                 sliderColors = PlatformSliderDefaults.defaultPlatformSliderColors(),
                 isExpandable = isPortrait,
                 modifier = modifier.fillMaxWidth(),
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 6114499..63ec54f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -31,6 +31,7 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.round
+import com.android.compose.animation.scene.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
 import com.android.compose.nestedscroll.PriorityNestedScrollConnection
 import kotlin.math.absoluteValue
 import kotlinx.coroutines.CoroutineScope
@@ -353,10 +354,7 @@
 
         // If the swipe was not committed or if the swipe distance is not computed yet, don't do
         // anything.
-        if (
-            swipeTransition._currentScene != toScene ||
-                distance == SwipeTransition.DistanceUnspecified
-        ) {
+        if (swipeTransition._currentScene != toScene || distance == DistanceUnspecified) {
             return fromScene to 0f
         }
 
@@ -418,7 +416,7 @@
             var targetScene: Scene
             var targetOffset: Float
             if (
-                distance != SwipeTransition.DistanceUnspecified &&
+                distance != DistanceUnspecified &&
                     shouldCommitSwipe(
                         offset,
                         distance,
@@ -444,8 +442,8 @@
                     if (targetScene == fromScene) {
                         0f
                     } else {
-                        check(distance != SwipeTransition.DistanceUnspecified) {
-                            "distance is equal to ${SwipeTransition.DistanceUnspecified}"
+                        check(distance != DistanceUnspecified) {
+                            "distance is equal to $DistanceUnspecified"
                         }
                         distance
                     }
@@ -628,6 +626,12 @@
     /** The spec to use when animating this transition to either [fromScene] or [toScene]. */
     lateinit var swipeSpec: SpringSpec<Float>
 
+    override val overscrollScope: OverscrollScope =
+        object : OverscrollScope {
+            override val absoluteDistance: Float
+                get() = distance().absoluteValue
+        }
+
     private var lastDistance = DistanceUnspecified
 
     /** Whether [TransitionState.Transition.finish] was called on this transition. */
@@ -753,10 +757,6 @@
         /** The job in which [animatable] is animated. */
         val job: Job,
     )
-
-    companion object {
-        const val DistanceUnspecified = 0f
-    }
 }
 
 private object DefaultSwipeDistance : UserActionDistance {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 86124df..e6f5d58 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -236,19 +236,28 @@
 
     interface HasOverscrollProperties {
         /**
-         * The position of the [TransitionState.Transition.toScene].
+         * The position of the [Transition.toScene].
          *
          * Used to understand the direction of the overscroll.
          */
         val isUpOrLeft: Boolean
 
         /**
-         * The relative orientation between [TransitionState.Transition.fromScene] and
-         * [TransitionState.Transition.toScene].
+         * The relative orientation between [Transition.fromScene] and [Transition.toScene].
          *
          * Used to understand the orientation of the overscroll.
          */
         val orientation: Orientation
+
+        /**
+         * Scope which can be used in the Overscroll DSL to define a transformation based on the
+         * distance between [Transition.fromScene] and [Transition.toScene].
+         */
+        val overscrollScope: OverscrollScope
+
+        companion object {
+            const val DistanceUnspecified = 0f
+        }
     }
 }
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index 2dd41cd..b466143 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -30,6 +30,7 @@
 import com.android.compose.animation.scene.transformation.DrawScale
 import com.android.compose.animation.scene.transformation.EdgeTranslate
 import com.android.compose.animation.scene.transformation.Fade
+import com.android.compose.animation.scene.transformation.OverscrollTranslate
 import com.android.compose.animation.scene.transformation.PropertyTransformation
 import com.android.compose.animation.scene.transformation.RangedPropertyTransformation
 import com.android.compose.animation.scene.transformation.ScaleSize
@@ -124,7 +125,7 @@
         overscrollSpecs.fastForEach { spec ->
             if (spec.orientation == orientation && filter(spec)) {
                 if (match != null) {
-                    error("Found multiple transition specs for transition $scene")
+                    error("Found multiple overscroll specs for overscroll $scene")
                 }
                 match = spec
             }
@@ -297,6 +298,7 @@
         ) {
             when (current) {
                 is Translate,
+                is OverscrollTranslate,
                 is EdgeTranslate,
                 is AnchoredTranslate -> {
                     throwIfNotNull(offset, element, name = "offset")
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index bc52a28..2c109a3 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -22,6 +22,7 @@
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
 
 /** Define the [transitions][SceneTransitions] to be used with a [SceneTransitionLayout]. */
 fun transitions(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions {
@@ -88,8 +89,7 @@
     ): OverscrollSpec
 }
 
-@TransitionDsl
-interface OverscrollBuilder : PropertyTransformationBuilder {
+interface BaseTransitionBuilder : PropertyTransformationBuilder {
     /**
      * The distance it takes for this transition to animate from 0% to 100% when it is driven by a
      * [UserAction].
@@ -120,7 +120,7 @@
 }
 
 @TransitionDsl
-interface TransitionBuilder : OverscrollBuilder, PropertyTransformationBuilder {
+interface TransitionBuilder : BaseTransitionBuilder {
     /**
      * The [AnimationSpec] used to animate the associated transition progress from `0` to `1` when
      * the transition is triggered (i.e. it is not gesture-based).
@@ -176,6 +176,24 @@
     fun reversed(builder: TransitionBuilder.() -> Unit)
 }
 
+@TransitionDsl
+interface OverscrollBuilder : BaseTransitionBuilder {
+    /** Translate the element(s) matching [matcher] by ([x], [y]) pixels. */
+    fun translate(
+        matcher: ElementMatcher,
+        x: OverscrollScope.() -> Float = { 0f },
+        y: OverscrollScope.() -> Float = { 0f },
+    )
+}
+
+interface OverscrollScope {
+    /**
+     * Return the absolute distance between fromScene and toScene, if available, otherwise
+     * [DistanceUnspecified].
+     */
+    val absoluteDistance: Float
+}
+
 /**
  * An interface to decide where we should draw shared Elements or compose MovableElements.
  *
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index 65e8ea5..1c9080f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -31,6 +31,7 @@
 import com.android.compose.animation.scene.transformation.DrawScale
 import com.android.compose.animation.scene.transformation.EdgeTranslate
 import com.android.compose.animation.scene.transformation.Fade
+import com.android.compose.animation.scene.transformation.OverscrollTranslate
 import com.android.compose.animation.scene.transformation.PropertyTransformation
 import com.android.compose.animation.scene.transformation.RangedPropertyTransformation
 import com.android.compose.animation.scene.transformation.ScaleSize
@@ -114,7 +115,7 @@
     }
 }
 
-internal open class OverscrollBuilderImpl : OverscrollBuilder {
+internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder {
     val transformations = mutableListOf<Transformation>()
     private var range: TransformationRange? = null
     protected var reversed = false
@@ -130,7 +131,7 @@
         range = null
     }
 
-    private fun transformation(transformation: PropertyTransformation<*>) {
+    protected fun transformation(transformation: PropertyTransformation<*>) {
         val transformation =
             if (range != null) {
                 RangedPropertyTransformation(transformation, range!!)
@@ -185,7 +186,7 @@
     }
 }
 
-internal class TransitionBuilderImpl : OverscrollBuilderImpl(), TransitionBuilder {
+internal class TransitionBuilderImpl : BaseTransitionBuilderImpl(), TransitionBuilder {
     override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow)
     override var swipeSpec: SpringSpec<Float>? = null
     override var distance: UserActionDistance? = null
@@ -226,3 +227,13 @@
         fractionRange(start, end, builder)
     }
 }
+
+internal open class OverscrollBuilderImpl : BaseTransitionBuilderImpl(), OverscrollBuilder {
+    override fun translate(
+        matcher: ElementMatcher,
+        x: OverscrollScope.() -> Float,
+        y: OverscrollScope.() -> Float
+    ) {
+        transformation(OverscrollTranslate(matcher, x, y))
+    }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
index 04d5914..849c9d7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
@@ -21,11 +21,11 @@
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.Element
 import com.android.compose.animation.scene.ElementMatcher
+import com.android.compose.animation.scene.OverscrollScope
 import com.android.compose.animation.scene.Scene
 import com.android.compose.animation.scene.SceneTransitionLayoutImpl
 import com.android.compose.animation.scene.TransitionState
 
-/** Translate an element by a fixed amount of density-independent pixels. */
 internal class Translate(
     override val matcher: ElementMatcher,
     private val x: Dp = 0.dp,
@@ -47,3 +47,28 @@
         }
     }
 }
+
+internal class OverscrollTranslate(
+    override val matcher: ElementMatcher,
+    val x: OverscrollScope.() -> Float = { 0f },
+    val y: OverscrollScope.() -> Float = { 0f },
+) : PropertyTransformation<Offset> {
+    override fun transform(
+        layoutImpl: SceneTransitionLayoutImpl,
+        scene: Scene,
+        element: Element,
+        sceneState: Element.SceneState,
+        transition: TransitionState.Transition,
+        value: Offset,
+    ): Offset {
+        // As this object is created by OverscrollBuilderImpl and we retrieve the current
+        // OverscrollSpec only when the transition implements HasOverscrollProperties, we can assume
+        // that this method was invoked after performing this check.
+        val overscrollProperties = transition as TransitionState.HasOverscrollProperties
+
+        return Offset(
+            x = value.x + overscrollProperties.overscrollScope.x(),
+            y = value.y + overscrollProperties.overscrollScope.y(),
+        )
+    }
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 059a10e..26e01fe 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -539,24 +539,20 @@
         }
     }
 
-    @Test
-    fun elementTransitionDuringOverscroll() {
+    private fun setupOverscrollScenario(
+        layoutWidth: Dp,
+        layoutHeight: Dp,
+        sceneTransitions: SceneTransitionsBuilder.() -> Unit,
+        firstScroll: Float
+    ): MutableSceneTransitionLayoutStateImpl {
         // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
         // detected as a drag event.
         var touchSlop = 0f
-        val overscrollTranslateY = 10.dp
-        val layoutWidth = 200.dp
-        val layoutHeight = 400.dp
 
         val state =
             MutableSceneTransitionLayoutState(
                 initialScene = TestScenes.SceneA,
-                transitions =
-                    transitions {
-                        overscroll(TestScenes.SceneB, Orientation.Vertical) {
-                            translate(TestElements.Foo, y = overscrollTranslateY)
-                        }
-                    }
+                transitions = transitions(sceneTransitions),
             )
                 as MutableSceneTransitionLayoutStateImpl
 
@@ -585,9 +581,30 @@
         rule.onRoot().performTouchInput {
             val middleTop = Offset((layoutWidth / 2).toPx(), 0f)
             down(middleTop)
-            // Scroll 50%
-            moveBy(Offset(0f, touchSlop + layoutHeight.toPx() * 0.5f), delayMillis = 1_000)
+            val firstScrollHeight = layoutHeight.toPx() * firstScroll
+            moveBy(Offset(0f, touchSlop + firstScrollHeight), delayMillis = 1_000)
         }
+        return state
+    }
+
+    @Test
+    fun elementTransitionDuringOverscroll() {
+        val layoutWidth = 200.dp
+        val layoutHeight = 400.dp
+        val overscrollTranslateY = 10.dp
+
+        val state =
+            setupOverscrollScenario(
+                layoutWidth = layoutWidth,
+                layoutHeight = layoutHeight,
+                sceneTransitions = {
+                    overscroll(TestScenes.SceneB, Orientation.Vertical) {
+                        // On overscroll 100% -> Foo should translate by overscrollTranslateY
+                        translate(TestElements.Foo, y = overscrollTranslateY)
+                    }
+                },
+                firstScroll = 0.5f, // Scroll 50%
+            )
 
         val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true)
         fooElement.assertTopPositionInRootIsEqualTo(0.dp)
@@ -691,4 +708,48 @@
         assertThat(state.currentOverscrollSpec).isNotNull()
         fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 1.5f)
     }
+
+    @Test
+    fun elementTransitionWithDistanceDuringOverscroll() {
+        val layoutWidth = 200.dp
+        val layoutHeight = 400.dp
+        val state =
+            setupOverscrollScenario(
+                layoutWidth = layoutWidth,
+                layoutHeight = layoutHeight,
+                sceneTransitions = {
+                    overscroll(TestScenes.SceneB, Orientation.Vertical) {
+                        // On overscroll 100% -> Foo should translate by layoutHeight
+                        translate(TestElements.Foo, y = { absoluteDistance })
+                    }
+                },
+                firstScroll = 1f, // 100% scroll
+            )
+
+        val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true)
+        fooElement.assertTopPositionInRootIsEqualTo(0.dp)
+
+        rule.onRoot().performTouchInput {
+            // Scroll another 50%
+            moveBy(Offset(0f, layoutHeight.toPx() * 0.5f), delayMillis = 1_000)
+        }
+
+        val transition = state.currentTransition
+        assertThat(transition).isNotNull()
+
+        // Scroll 150% (100% scroll + 50% overscroll)
+        assertThat(transition!!.progress).isEqualTo(1.5f)
+        assertThat(state.currentOverscrollSpec).isNotNull()
+        fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * 0.5f)
+
+        rule.onRoot().performTouchInput {
+            // Scroll another 100%
+            moveBy(Offset(0f, layoutHeight.toPx()), delayMillis = 1_000)
+        }
+
+        // Scroll 250% (100% scroll + 150% overscroll)
+        assertThat(transition.progress).isEqualTo(2.5f)
+        assertThat(state.currentOverscrollSpec).isNotNull()
+        fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * 1.5f)
+    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
index c9c3ecc..825fe13 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
@@ -22,9 +22,9 @@
 import androidx.compose.animation.core.tween
 import androidx.compose.foundation.gestures.Orientation
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.transformation.OverscrollTranslate
 import com.android.compose.animation.scene.transformation.Transformation
 import com.android.compose.animation.scene.transformation.TransformationRange
-import com.android.compose.animation.scene.transformation.Translate
 import com.google.common.truth.Correspondence
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
@@ -228,12 +228,14 @@
     @Test
     fun overscrollSpec() {
         val transitions = transitions {
-            overscroll(TestScenes.SceneA, Orientation.Vertical) { translate(TestElements.Bar) }
+            overscroll(TestScenes.SceneA, Orientation.Vertical) {
+                translate(TestElements.Bar, x = { 1f }, y = { 2f })
+            }
         }
 
         val overscrollSpec = transitions.overscrollSpecs.single()
         val transformation = overscrollSpec.transformationSpec.transformations.single()
-        assertThat(transformation).isInstanceOf(Translate::class.java)
+        assertThat(transformation).isInstanceOf(OverscrollTranslate::class.java)
     }
 
     companion object {
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt
index 153d2b8..73a66c6 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt
@@ -38,6 +38,10 @@
         override val isUserInputOngoing: Boolean = isUserInputOngoing
         override val isUpOrLeft: Boolean = isUpOrLeft
         override val orientation: Orientation = orientation
+        override val overscrollScope: OverscrollScope =
+            object : OverscrollScope {
+                override val absoluteDistance = 0f
+            }
 
         override fun finish(): Job {
             error("finish() is not supported in test transitions")
diff --git a/packages/SystemUI/customization/res/values/ids.xml b/packages/SystemUI/customization/res/values/ids.xml
new file mode 100644
index 0000000..5eafbfc
--- /dev/null
+++ b/packages/SystemUI/customization/res/values/ids.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- View ids for elements in large weather clock -->
+    <item type="id" name="weather_clock_time" />
+    <item type="id" name="weather_clock_date" />
+    <item type="id" name="weather_clock_weather_icon" />
+    <item type="id" name="weather_clock_temperature" />
+    <item type="id" name="weather_clock_alarm_dnd" />
+</resources>
\ No newline at end of file
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/bouncer/domain/interactor/SimBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
index 09fdd11..bd1403a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -201,6 +202,7 @@
     @Test
     fun verifySimPin() =
         testScope.runTest {
+            val msg by collectLastValue(underTest.bouncerMessageChanged)
             bouncerSimRepository.setSubscriptionId(1)
             bouncerSimRepository.setSimPukLocked(false)
             whenever(telephonyManager.createForSubscriptionId(anyInt()))
@@ -208,8 +210,7 @@
             whenever(telephonyManager.supplyIccLockPin(anyString()))
                 .thenReturn(PinResult(PinResult.PIN_RESULT_TYPE_SUCCESS, 1))
 
-            val msg: String? = underTest.verifySim(listOf(0, 0, 0, 0))
-            runCurrent()
+            verifySim(listOf(0, 0, 0, 0))
             assertThat(msg).isNull()
 
             verify(keyguardUpdateMonitor).reportSimUnlocked(1)
@@ -218,6 +219,7 @@
     @Test
     fun verifySimPin_incorrect_oneRemainingAttempt() =
         testScope.runTest {
+            val msg by collectLastValue(underTest.bouncerMessageChanged)
             bouncerSimRepository.setSubscriptionId(1)
             bouncerSimRepository.setSimPukLocked(false)
             whenever(telephonyManager.createForSubscriptionId(anyInt()))
@@ -229,9 +231,7 @@
                         1,
                     )
                 )
-
-            val msg: String? = underTest.verifySim(listOf(0, 0, 0, 0))
-            runCurrent()
+            verifySim(listOf(0, 0, 0, 0))
 
             assertThat(msg).isNull()
             val errorDialogMessage by collectLastValue(bouncerSimRepository.errorDialogMessage)
@@ -245,6 +245,7 @@
     @Test
     fun verifySimPin_incorrect_threeRemainingAttempts() =
         testScope.runTest {
+            val msg by collectLastValue(underTest.bouncerMessageChanged)
             bouncerSimRepository.setSubscriptionId(1)
             bouncerSimRepository.setSimPukLocked(false)
             whenever(telephonyManager.createForSubscriptionId(anyInt()))
@@ -257,8 +258,7 @@
                     )
                 )
 
-            val msg = underTest.verifySim(listOf(0, 0, 0, 0))
-            runCurrent()
+            verifySim(listOf(0, 0, 0, 0))
 
             assertThat(msg).isEqualTo("Enter SIM PIN. You have 3 remaining attempts.")
         }
@@ -266,10 +266,11 @@
     @Test
     fun verifySimPin_notCorrectLength_tooShort() =
         testScope.runTest {
+            val msg by collectLastValue(underTest.bouncerMessageChanged)
             bouncerSimRepository.setSubscriptionId(1)
             bouncerSimRepository.setSimPukLocked(false)
 
-            val msg = underTest.verifySim(listOf(0))
+            verifySim(listOf(0))
 
             assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_pin_hint))
         }
@@ -277,10 +278,12 @@
     @Test
     fun verifySimPin_notCorrectLength_tooLong() =
         testScope.runTest {
+            val msg by collectLastValue(underTest.bouncerMessageChanged)
+
             bouncerSimRepository.setSubscriptionId(1)
             bouncerSimRepository.setSimPukLocked(false)
 
-            val msg = underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
+            verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
 
             assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_pin_hint))
         }
@@ -288,6 +291,7 @@
     @Test
     fun verifySimPuk() =
         testScope.runTest {
+            val msg by collectLastValue(underTest.bouncerMessageChanged)
             whenever(telephonyManager.createForSubscriptionId(anyInt()))
                 .thenReturn(telephonyManager)
             whenever(telephonyManager.supplyIccLockPuk(anyString(), anyString()))
@@ -295,13 +299,13 @@
             bouncerSimRepository.setSubscriptionId(1)
             bouncerSimRepository.setSimPukLocked(true)
 
-            var msg = underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
+            verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
             assertThat(msg).isEqualTo(resources.getString(R.string.kg_puk_enter_pin_hint))
 
-            msg = underTest.verifySim(listOf(0, 0, 0, 0))
+            verifySim(listOf(0, 0, 0, 0))
             assertThat(msg).isEqualTo(resources.getString(R.string.kg_enter_confirm_pin_hint))
 
-            msg = underTest.verifySim(listOf(0, 0, 0, 0))
+            verifySim(listOf(0, 0, 0, 0))
             assertThat(msg).isNull()
 
             runCurrent()
@@ -311,37 +315,49 @@
     @Test
     fun verifySimPuk_inputTooShort() =
         testScope.runTest {
+            val msg by collectLastValue(underTest.bouncerMessageChanged)
+
             bouncerSimRepository.setSubscriptionId(1)
             bouncerSimRepository.setSimPukLocked(true)
-            val msg = underTest.verifySim(listOf(0, 0, 0, 0))
+
+            verifySim(listOf(0, 0, 0, 0))
             assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_puk_hint))
         }
 
     @Test
     fun verifySimPuk_pinNotCorrectLength() =
         testScope.runTest {
+            val msg by collectLastValue(underTest.bouncerMessageChanged)
             bouncerSimRepository.setSubscriptionId(1)
             bouncerSimRepository.setSimPukLocked(true)
 
-            underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
+            verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
 
-            val msg = underTest.verifySim(listOf(0, 0, 0))
+            verifySim(listOf(0, 0, 0))
+
             assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_pin_hint))
         }
 
     @Test
     fun verifySimPuk_confirmedPinDoesNotMatch() =
         testScope.runTest {
+            val msg by collectLastValue(underTest.bouncerMessageChanged)
+
             bouncerSimRepository.setSubscriptionId(1)
             bouncerSimRepository.setSimPukLocked(true)
 
-            underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
-            underTest.verifySim(listOf(0, 0, 0, 0))
+            verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
+            verifySim(listOf(0, 0, 0, 0))
 
-            val msg = underTest.verifySim(listOf(0, 0, 0, 1))
+            verifySim(listOf(0, 0, 0, 1))
             assertThat(msg).isEqualTo(resources.getString(R.string.kg_puk_enter_pin_hint))
         }
 
+    private suspend fun TestScope.verifySim(pinDigits: List<Int>) {
+        runCurrent()
+        underTest.verifySim(pinDigits)
+    }
+
     @Test
     fun onErrorDialogDismissed_clearsErrorDialogMessageInRepository() {
         bouncerSimRepository.setSimVerificationErrorMessage("abc")
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..d624bf7 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,95 @@
             }
         }
 
+    @Test
+    fun hubTimeout_whenDreaming() =
+        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_dreamStopped() =
+        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_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 +358,8 @@
             setCommunalAvailable(true)
             runCurrent()
         }
+
+    companion object {
+        private const val SCREEN_TIMEOUT = 1000
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorTest.kt
index 2e9ee5c..4a7757b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorTest.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -35,6 +36,7 @@
     private val kosmos = testKosmos()
     private val biometricSettingsRepository = kosmos.biometricSettingsRepository
     private val underTest = kosmos.deviceEntryBiometricSettingsInteractor
+    private val testScope = kosmos.testScope
 
     @Test
     fun isCoex_true() = runTest {
@@ -59,4 +61,25 @@
         biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
         assertThat(isCoex).isFalse()
     }
+
+    @Test
+    fun authenticationFlags_providesAuthFlagsFromRepository() =
+        testScope.runTest {
+            assertThat(underTest.authenticationFlags)
+                .isSameInstanceAs(biometricSettingsRepository.authenticationFlags)
+        }
+
+    @Test
+    fun isFaceAuthEnrolledAndEnabled_providesValueFromRepository() =
+        testScope.runTest {
+            assertThat(underTest.isFaceAuthEnrolledAndEnabled)
+                .isSameInstanceAs(biometricSettingsRepository.isFaceAuthEnrolledAndEnabled)
+        }
+
+    @Test
+    fun isFingerprintAuthEnrolledAndEnabled_providesValueFromRepository() =
+        testScope.runTest {
+            assertThat(underTest.isFingerprintAuthEnrolledAndEnabled)
+                .isSameInstanceAs(biometricSettingsRepository.isFingerprintEnrolledAndEnabled)
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
index 4f44705..70ceb2a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -19,6 +19,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.SceneKey
+import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -27,8 +28,21 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.AdaptiveAuthRequest
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.BouncerLockedOut
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.PolicyLockdown
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.SecurityTimeout
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.TrustAgentDisabled
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.UnattendedUpdate
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.UserLockdown
+import com.android.systemui.flags.fakeSystemPropertiesHelper
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.fakeTrustRepository
+import com.android.systemui.keyguard.shared.model.AuthenticationFlags
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
@@ -36,6 +50,7 @@
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -230,8 +245,8 @@
             assertThat(canSwipeToEnter).isFalse()
 
             trustRepository.setCurrentUserTrusted(true)
-            runCurrent()
             faceAuthRepository.isAuthenticated.value = false
+            runCurrent()
 
             assertThat(canSwipeToEnter).isTrue()
         }
@@ -383,6 +398,204 @@
             assertThat(isUnlocked).isTrue()
         }
 
+    @Test
+    fun deviceEntryRestrictionReason_whenFaceOrFingerprintOrTrust_alwaysNull() =
+        testScope.runTest {
+            kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+            kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+            runCurrent()
+
+            verifyRestrictionReasonsForAuthFlags(
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to null,
+                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to null,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to null,
+                LockPatternUtils.StrongAuthTracker
+                    .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to null,
+                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+                    null,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+                    null,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to null
+            )
+        }
+
+    @Test
+    fun deviceEntryRestrictionReason_whenFaceIsEnrolledAndEnabled_mapsToAuthFlagsState() =
+        testScope.runTest {
+            kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+            kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+            kosmos.fakeSystemPropertiesHelper.set(
+                DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
+                "not mainline reboot"
+            )
+            runCurrent()
+
+            verifyRestrictionReasonsForAuthFlags(
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
+                    DeviceNotUnlockedSinceReboot,
+                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
+                    AdaptiveAuthRequest,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
+                    BouncerLockedOut,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+                    SecurityTimeout,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+                    UserLockdown,
+                LockPatternUtils.StrongAuthTracker
+                    .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+                    NonStrongBiometricsSecurityTimeout,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+                    UnattendedUpdate,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+                    PolicyLockdown,
+                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+                    null,
+            )
+        }
+
+    @Test
+    fun deviceEntryRestrictionReason_whenFingerprintIsEnrolledAndEnabled_mapsToAuthFlagsState() =
+        testScope.runTest {
+            kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+            kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+            kosmos.fakeSystemPropertiesHelper.set(
+                DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
+                "not mainline reboot"
+            )
+            runCurrent()
+
+            verifyRestrictionReasonsForAuthFlags(
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
+                    DeviceNotUnlockedSinceReboot,
+                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
+                    AdaptiveAuthRequest,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
+                    BouncerLockedOut,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+                    SecurityTimeout,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+                    UserLockdown,
+                LockPatternUtils.StrongAuthTracker
+                    .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+                    NonStrongBiometricsSecurityTimeout,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+                    UnattendedUpdate,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+                    PolicyLockdown,
+                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+                    null,
+            )
+        }
+
+    @Test
+    fun deviceEntryRestrictionReason_whenTrustAgentIsEnabled_mapsToAuthFlagsState() =
+        testScope.runTest {
+            kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+            kosmos.fakeTrustRepository.setTrustUsuallyManaged(true)
+            kosmos.fakeTrustRepository.setCurrentUserTrustManaged(false)
+            kosmos.fakeSystemPropertiesHelper.set(
+                DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
+                "not mainline reboot"
+            )
+            runCurrent()
+
+            verifyRestrictionReasonsForAuthFlags(
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
+                    DeviceNotUnlockedSinceReboot,
+                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
+                    AdaptiveAuthRequest,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
+                    BouncerLockedOut,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+                    SecurityTimeout,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+                    UserLockdown,
+                LockPatternUtils.StrongAuthTracker
+                    .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+                    NonStrongBiometricsSecurityTimeout,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+                    UnattendedUpdate,
+                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+                    PolicyLockdown,
+                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to
+                    TrustAgentDisabled,
+                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+                    TrustAgentDisabled,
+            )
+        }
+
+    @Test
+    fun deviceEntryRestrictionReason_whenDeviceRebootedForMainlineUpdate_mapsToTheCorrectReason() =
+        testScope.runTest {
+            val deviceEntryRestrictionReason by
+                collectLastValue(underTest.deviceEntryRestrictionReason)
+            kosmos.fakeSystemPropertiesHelper.set(
+                DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
+                DeviceEntryInteractor.REBOOT_MAINLINE_UPDATE
+            )
+            kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
+                AuthenticationFlags(
+                    userId = 1,
+                    flag = LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+                )
+            )
+            runCurrent()
+
+            kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+            kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+            runCurrent()
+
+            assertThat(deviceEntryRestrictionReason).isNull()
+
+            kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+            runCurrent()
+
+            assertThat(deviceEntryRestrictionReason)
+                .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
+
+            kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+            runCurrent()
+
+            assertThat(deviceEntryRestrictionReason)
+                .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
+
+            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+            kosmos.fakeTrustRepository.setTrustUsuallyManaged(true)
+            runCurrent()
+
+            assertThat(deviceEntryRestrictionReason)
+                .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
+        }
+
+    private fun TestScope.verifyRestrictionReasonsForAuthFlags(
+        vararg authFlagToDeviceEntryRestriction: Pair<Int, DeviceEntryRestrictionReason?>
+    ) {
+        val deviceEntryRestrictionReason by collectLastValue(underTest.deviceEntryRestrictionReason)
+
+        authFlagToDeviceEntryRestriction.forEach { (flag, expectedReason) ->
+            kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
+                AuthenticationFlags(userId = 1, flag = flag)
+            )
+            runCurrent()
+
+            if (expectedReason == null) {
+                assertThat(deviceEntryRestrictionReason).isNull()
+            } else {
+                assertThat(deviceEntryRestrictionReason).isEqualTo(expectedReason)
+            }
+        }
+    }
+
     private fun switchToScene(sceneKey: SceneKey) {
         sceneInteractor.changeScene(sceneKey, "reason")
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
index 87b1bbb..1adf414 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
@@ -40,7 +40,6 @@
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
 import java.util.Optional
@@ -50,7 +49,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -136,12 +134,17 @@
 
     @Test
     @DisableFlags(FLAG_HOME_PANEL_DREAM)
-    fun testStartDoesNotRunDreamServiceWhenFlagIsDisabled() =
+    fun testStartDisablesDreamServiceWhenFlagIsDisabled() =
         testScope.runTest {
             selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_NON_PANEL)
             startable.start()
             runCurrent()
-            verify(packageManager, never()).setComponentEnabledSetting(any(), any(), any())
+            verify(packageManager)
+                .setComponentEnabledSetting(
+                    eq(componentName),
+                    eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
+                    eq(PackageManager.DONT_KILL_APP)
+                )
         }
 
     private fun ControlsServiceInfo(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
new file mode 100644
index 0000000..8f03717
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
@@ -0,0 +1,332 @@
+/*
+ * 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.haptics.qs
+
+import android.os.VibrationEffect
+import android.testing.TestableLooper.RunWithLooper
+import android.view.MotionEvent
+import android.view.View
+import androidx.test.core.view.MotionEventBuilder
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.AnimatorTestRule
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWithLooper(setAsMainLooper = true)
+class QSLongPressEffectTest : SysuiTestCase() {
+
+    @Rule @JvmField val mMockitoRule: MockitoRule = MockitoJUnit.rule()
+    @Mock private lateinit var vibratorHelper: VibratorHelper
+    @Mock private lateinit var testView: View
+    @get:Rule val animatorTestRule = AnimatorTestRule(this)
+    private val kosmos = testKosmos()
+
+    private val effectDuration = 400
+    private val lowTickDuration = 12
+    private val spinDuration = 133
+
+    private lateinit var longPressEffect: QSLongPressEffect
+
+    @Before
+    fun setup() {
+        whenever(
+                vibratorHelper.getPrimitiveDurations(
+                    VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+                    VibrationEffect.Composition.PRIMITIVE_SPIN,
+                )
+            )
+            .thenReturn(intArrayOf(lowTickDuration, spinDuration))
+
+        longPressEffect =
+            QSLongPressEffect(
+                vibratorHelper,
+                effectDuration,
+            )
+    }
+
+    @Test
+    fun onActionDown_whileIdle_startsWait() = testWithScope {
+        // GIVEN an action down event occurs
+        val downEvent = buildMotionEvent(MotionEvent.ACTION_DOWN)
+        longPressEffect.onTouch(testView, downEvent)
+
+        // THEN the effect moves to the TIMEOUT_WAIT state
+        assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.TIMEOUT_WAIT)
+    }
+
+    @Test
+    fun onActionCancel_whileWaiting_goesIdle() = testWhileWaiting {
+        // GIVEN an action cancel occurs
+        val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
+        longPressEffect.onTouch(testView, cancelEvent)
+
+        // THEN the effect goes back to idle and does not start
+        assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
+        assertEffectDidNotStart()
+    }
+
+    @Test
+    fun onActionUp_whileWaiting_performsClick() = testWhileWaiting {
+        // GIVEN an action is being collected
+        val action by collectLastValue(longPressEffect.actionType)
+
+        // GIVEN an action up occurs
+        val upEvent = buildMotionEvent(MotionEvent.ACTION_UP)
+        longPressEffect.onTouch(testView, upEvent)
+
+        // THEN the action to invoke is the click action and the effect does not start
+        assertThat(action).isEqualTo(QSLongPressEffect.ActionType.CLICK)
+        assertEffectDidNotStart()
+    }
+
+    @Test
+    fun onWaitComplete_whileWaiting_beginsEffect() = testWhileWaiting {
+        // GIVEN the pressed timeout is complete
+        advanceTimeBy(QSLongPressEffect.PRESSED_TIMEOUT + 10L)
+
+        // THEN the effect starts
+        assertEffectStarted()
+    }
+
+    @Test
+    fun onActionUp_whileEffectHasBegun_reversesEffect() = testWhileRunning {
+        // GIVEN that the effect is at the middle of its completion (progress of 50%)
+        animatorTestRule.advanceTimeBy(effectDuration / 2L)
+
+        // WHEN an action up occurs
+        val upEvent = buildMotionEvent(MotionEvent.ACTION_UP)
+        longPressEffect.onTouch(testView, upEvent)
+
+        // THEN the effect gets reversed at 50% progress
+        assertEffectReverses(0.5f)
+    }
+
+    @Test
+    fun onActionCancel_whileEffectHasBegun_reversesEffect() = testWhileRunning {
+        // GIVEN that the effect is at the middle of its completion (progress of 50%)
+        animatorTestRule.advanceTimeBy(effectDuration / 2L)
+
+        // WHEN an action cancel occurs
+        val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
+        longPressEffect.onTouch(testView, cancelEvent)
+
+        // THEN the effect gets reversed at 50% progress
+        assertEffectReverses(0.5f)
+    }
+
+    @Test
+    fun onAnimationComplete_effectEnds() = testWhileRunning {
+        // GIVEN that the animation completes
+        animatorTestRule.advanceTimeBy(effectDuration + 10L)
+
+        // THEN the long-press effect completes
+        assertEffectCompleted()
+    }
+
+    @Test
+    fun onActionDown_whileRunningBackwards_resets() = testWhileRunning {
+        // GIVEN that the effect is at the middle of its completion (progress of 50%)
+        animatorTestRule.advanceTimeBy(effectDuration / 2L)
+
+        // GIVEN an action cancel occurs and the effect gets reversed
+        val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
+        longPressEffect.onTouch(testView, cancelEvent)
+
+        // GIVEN an action down occurs
+        val downEvent = buildMotionEvent(MotionEvent.ACTION_DOWN)
+        longPressEffect.onTouch(testView, downEvent)
+
+        // THEN the effect resets
+        assertEffectResets()
+    }
+
+    @Test
+    fun onAnimationComplete_whileRunningBackwards_goesToIdle() = testWhileRunning {
+        // GIVEN that the effect is at the middle of its completion (progress of 50%)
+        animatorTestRule.advanceTimeBy(effectDuration / 2L)
+
+        // GIVEN an action cancel occurs and the effect gets reversed
+        val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
+        longPressEffect.onTouch(testView, cancelEvent)
+
+        // GIVEN that the animation completes after a sufficient amount of time
+        animatorTestRule.advanceTimeBy(effectDuration.toLong())
+
+        // THEN the state goes to [QSLongPressEffect.State.IDLE]
+        assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
+    }
+
+    private fun buildMotionEvent(action: Int): MotionEvent =
+        MotionEventBuilder.newBuilder().setAction(action).build()
+
+    private fun testWithScope(test: suspend TestScope.() -> Unit) =
+        with(kosmos) {
+            testScope.runTest {
+                // GIVEN an effect with a testing scope
+                longPressEffect.scope = CoroutineScope(UnconfinedTestDispatcher(testScheduler))
+
+                // THEN run the test
+                test()
+            }
+        }
+
+    private fun testWhileWaiting(test: suspend TestScope.() -> Unit) =
+        with(kosmos) {
+            testScope.runTest {
+                // GIVEN an effect with a testing scope
+                longPressEffect.scope = CoroutineScope(UnconfinedTestDispatcher(testScheduler))
+
+                // GIVEN the TIMEOUT_WAIT state is entered
+                val downEvent =
+                    MotionEventBuilder.newBuilder().setAction(MotionEvent.ACTION_DOWN).build()
+                longPressEffect.onTouch(testView, downEvent)
+
+                // THEN run the test
+                test()
+            }
+        }
+
+    private fun testWhileRunning(test: suspend TestScope.() -> Unit) =
+        with(kosmos) {
+            testScope.runTest {
+                // GIVEN an effect with a testing scope
+                longPressEffect.scope = CoroutineScope(UnconfinedTestDispatcher(testScheduler))
+
+                // GIVEN the down event that enters the TIMEOUT_WAIT state
+                val downEvent =
+                    MotionEventBuilder.newBuilder().setAction(MotionEvent.ACTION_DOWN).build()
+                longPressEffect.onTouch(testView, downEvent)
+
+                // GIVEN that the timeout completes and the effect starts
+                advanceTimeBy(QSLongPressEffect.PRESSED_TIMEOUT + 10L)
+
+                // THEN run the test
+                test()
+            }
+        }
+
+    /**
+     * Asserts that the effect started by checking that:
+     * 1. The effect progress is 0f
+     * 2. Initial hint haptics are played
+     * 3. The internal state is [QSLongPressEffect.State.RUNNING_FORWARD]
+     */
+    private fun TestScope.assertEffectStarted() {
+        val effectProgress by collectLastValue(longPressEffect.effectProgress)
+        val longPressHint =
+            LongPressHapticBuilder.createLongPressHint(
+                lowTickDuration,
+                spinDuration,
+                effectDuration,
+            )
+
+        assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.RUNNING_FORWARD)
+        assertThat(effectProgress).isEqualTo(0f)
+        assertThat(longPressHint).isNotNull()
+        verify(vibratorHelper).vibrate(longPressHint!!)
+    }
+
+    /**
+     * Asserts that the effect did not start by checking that:
+     * 1. No effect progress is emitted
+     * 2. No haptics are played
+     * 3. The internal state is not [QSLongPressEffect.State.RUNNING_BACKWARDS] or
+     *    [QSLongPressEffect.State.RUNNING_FORWARD]
+     */
+    private fun TestScope.assertEffectDidNotStart() {
+        val effectProgress by collectLastValue(longPressEffect.effectProgress)
+
+        assertThat(longPressEffect.state).isNotEqualTo(QSLongPressEffect.State.RUNNING_FORWARD)
+        assertThat(longPressEffect.state).isNotEqualTo(QSLongPressEffect.State.RUNNING_BACKWARDS)
+        assertThat(effectProgress).isNull()
+        verify(vibratorHelper, never()).vibrate(any(/* type= */ VibrationEffect::class.java))
+    }
+
+    /**
+     * Asserts that the effect completes by checking that:
+     * 1. The progress is null
+     * 2. The final snap haptics are played
+     * 3. The internal state goes back to [QSLongPressEffect.State.IDLE]
+     * 4. The action to perform on the tile is the long-press action
+     */
+    private fun TestScope.assertEffectCompleted() {
+        val action by collectLastValue(longPressEffect.actionType)
+        val effectProgress by collectLastValue(longPressEffect.effectProgress)
+        val snapEffect = LongPressHapticBuilder.createSnapEffect()
+
+        assertThat(effectProgress).isNull()
+        assertThat(snapEffect).isNotNull()
+        verify(vibratorHelper).vibrate(snapEffect!!)
+        assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
+        assertThat(action).isEqualTo(QSLongPressEffect.ActionType.LONG_PRESS)
+    }
+
+    /**
+     * Assert that the effect gets reverted by checking that:
+     * 1. The internal state is [QSLongPressEffect.State.RUNNING_BACKWARDS]
+     * 2. The reverse haptics plays at the point where the animation was paused
+     */
+    private fun assertEffectReverses(pausedProgress: Float) {
+        val reverseHaptics =
+            LongPressHapticBuilder.createReversedEffect(
+                pausedProgress,
+                lowTickDuration,
+                effectDuration,
+            )
+
+        assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.RUNNING_BACKWARDS)
+        assertThat(reverseHaptics).isNotNull()
+        verify(vibratorHelper).vibrate(reverseHaptics!!)
+    }
+
+    /**
+     * Asserts that the effect resets by checking that:
+     * 1. The effect progress resets to 0
+     * 2. The internal state goes back to [QSLongPressEffect.State.TIMEOUT_WAIT]
+     */
+    private fun TestScope.assertEffectResets() {
+        val effectProgress by collectLastValue(longPressEffect.effectProgress)
+        assertThat(effectProgress).isEqualTo(0f)
+
+        assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.TIMEOUT_WAIT)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 19950a5..2fd2ef1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -19,9 +19,12 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import android.platform.test.annotations.EnableFlags
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
@@ -31,86 +34,129 @@
 import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.data.repository.shadeRepository
 import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.shade.domain.startable.shadeStartable
+import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
+import org.junit.BeforeClass
 import org.junit.Test
 import org.junit.runner.RunWith
+import platform.test.runner.parameterized.Parameter
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @SmallTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(ParameterizedAndroidJunit4::class)
 class LockscreenSceneViewModelTest : SysuiTestCase() {
 
+    companion object {
+        @Parameters(
+            name =
+                "canSwipeToEnter={0}, downWithTwoPointers={1}, downFromEdge={2}," +
+                    " isSingleShade={3}, isCommunalAvailable={4}"
+        )
+        @JvmStatic
+        fun combinations() = buildList {
+            repeat(32) { combination ->
+                add(
+                    arrayOf(
+                        /* canSwipeToEnter= */ combination and 1 != 0,
+                        /* downWithTwoPointers= */ combination and 2 != 0,
+                        /* downFromEdge= */ combination and 4 != 0,
+                        /* isSingleShade= */ combination and 8 != 0,
+                        /* isCommunalAvailable= */ combination and 16 != 0,
+                    )
+                )
+            }
+        }
+
+        @JvmStatic
+        @BeforeClass
+        fun setUp() {
+            val combinationStrings =
+                combinations().map { array ->
+                    check(array.size == 5)
+                    "${array[4]},${array[3]},${array[2]},${array[1]},${array[0]}"
+                }
+            val uniqueCombinations = combinationStrings.toSet()
+            assertThat(combinationStrings).hasSize(uniqueCombinations.size)
+        }
+
+        private fun expectedDownDestination(
+            downFromEdge: Boolean,
+            isSingleShade: Boolean,
+        ): SceneKey {
+            return if (downFromEdge && isSingleShade) Scenes.QuickSettings else Scenes.Shade
+        }
+    }
+
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val sceneInteractor by lazy { kosmos.sceneInteractor }
 
+    @JvmField @Parameter(0) var canSwipeToEnter: Boolean = false
+    @JvmField @Parameter(1) var downWithTwoPointers: Boolean = false
+    @JvmField @Parameter(2) var downFromEdge: Boolean = false
+    @JvmField @Parameter(3) var isSingleShade: Boolean = true
+    @JvmField @Parameter(4) var isCommunalAvailable: Boolean = false
+
     private val underTest by lazy { createLockscreenSceneViewModel() }
 
     @Test
-    fun upTransitionSceneKey_canSwipeToUnlock_gone() =
+    @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
+    fun destinationScenes() =
         testScope.runTest {
-            val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
-            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.None
-            )
             kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
-            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
-            sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
-
-            assertThat(upTransitionSceneKey).isEqualTo(Scenes.Gone)
-        }
-
-    @Test
-    fun upTransitionSceneKey_cannotSwipeToUnlock_bouncer() =
-        testScope.runTest {
-            val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Pin
+                if (canSwipeToEnter) {
+                    AuthenticationMethodModel.None
+                } else {
+                    AuthenticationMethodModel.Pin
+                }
             )
-            kosmos.fakeDeviceEntryRepository.setUnlocked(false)
+            kosmos.fakeDeviceEntryRepository.setUnlocked(canSwipeToEnter)
             sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
+            kosmos.shadeRepository.setShadeMode(
+                if (isSingleShade) {
+                    ShadeMode.Single
+                } else {
+                    ShadeMode.Split
+                }
+            )
+            kosmos.setCommunalAvailable(isCommunalAvailable)
 
-            assertThat(upTransitionSceneKey).isEqualTo(Scenes.Bouncer)
-        }
+            val destinationScenes by collectLastValue(underTest.destinationScenes)
 
-    @EnableFlags(FLAG_COMMUNAL_HUB)
-    @Test
-    fun leftTransitionSceneKey_communalIsAvailable_communal() =
-        testScope.runTest {
-            val leftDestinationSceneKey by collectLastValue(underTest.leftDestinationSceneKey)
-            assertThat(leftDestinationSceneKey).isNull()
+            assertThat(
+                    destinationScenes
+                        ?.get(
+                            Swipe(
+                                SwipeDirection.Down,
+                                fromSource = Edge.Top.takeIf { downFromEdge },
+                                pointerCount = if (downWithTwoPointers) 2 else 1,
+                            )
+                        )
+                        ?.toScene
+                )
+                .isEqualTo(
+                    expectedDownDestination(
+                        downFromEdge = downFromEdge,
+                        isSingleShade = isSingleShade,
+                    )
+                )
 
-            kosmos.setCommunalAvailable(true)
-            runCurrent()
-            assertThat(leftDestinationSceneKey).isEqualTo(Scenes.Communal)
-        }
+            assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
+                .isEqualTo(if (canSwipeToEnter) Scenes.Gone else Scenes.Bouncer)
 
-    @Test
-    fun downFromTopEdgeDestinationSceneKey_whenNotSplitShade_quickSettings() =
-        testScope.runTest {
-            overrideResource(R.bool.config_use_split_notification_shade, false)
-            kosmos.shadeStartable.start()
-            val sceneKey by collectLastValue(underTest.downFromTopEdgeDestinationSceneKey)
-            assertThat(sceneKey).isEqualTo(Scenes.QuickSettings)
-        }
-
-    @Test
-    fun downFromTopEdgeDestinationSceneKey_whenSplitShade_null() =
-        testScope.runTest {
-            overrideResource(R.bool.config_use_split_notification_shade, true)
-            kosmos.shadeStartable.start()
-            val sceneKey by collectLastValue(underTest.downFromTopEdgeDestinationSceneKey)
-            assertThat(sceneKey).isNull()
+            assertThat(destinationScenes?.get(Swipe(SwipeDirection.Left))?.toScene)
+                .isEqualTo(Scenes.Communal.takeIf { isCommunalAvailable })
         }
 
     private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
index a1f885c..c0e5a9b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
@@ -114,7 +114,7 @@
                 DisabledByPolicyInteractor.PolicyResult.TileDisabled(ADMIN)
             )
 
-        val expectedIntent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(context, ADMIN)
+        val expectedIntent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(ADMIN)
         assertThat(result).isTrue()
         verify(activityStarter).postStartActivityDismissingKeyguard(intentCaptor.capture(), any())
         assertThat(intentCaptor.value.filterEquals(expectedIntent)).isTrue()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 42c3354..af9abcd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -26,7 +26,6 @@
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
 import com.android.internal.R
 import com.android.internal.util.EmergencyAffordanceManager
 import com.android.internal.util.emergencyAffordanceManager
@@ -317,8 +316,8 @@
     @Test
     fun swipeUpOnLockscreen_enterCorrectPin_unlocksDevice() =
         testScope.runTest {
-            val upDestinationSceneKey by
-                collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+            val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+            val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
             emulateUserDrivenTransition(
                 to = upDestinationSceneKey,
@@ -337,8 +336,8 @@
         testScope.runTest {
             setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
 
-            val upDestinationSceneKey by
-                collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+            val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+            val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
             emulateUserDrivenTransition(
                 to = upDestinationSceneKey,
@@ -356,7 +355,7 @@
             emulateUserDrivenTransition(to = Scenes.Shade)
             assertCurrentScene(Scenes.Shade)
 
-            val upDestinationSceneKey = destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene
+            val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Lockscreen)
             emulateUserDrivenTransition(
                 to = upDestinationSceneKey,
@@ -379,7 +378,7 @@
             emulateUserDrivenTransition(to = Scenes.Shade)
             assertCurrentScene(Scenes.Shade)
 
-            val upDestinationSceneKey = destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene
+            val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
             emulateUserDrivenTransition(
                 to = upDestinationSceneKey,
@@ -447,8 +446,8 @@
     fun swipeUpOnLockscreenWhileUnlocked_dismissesLockscreen() =
         testScope.runTest {
             unlockDevice()
-            val upDestinationSceneKey by
-                collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+            val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+            val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
         }
 
@@ -469,8 +468,8 @@
     fun dismissingIme_whileOnPasswordBouncer_navigatesToLockscreen() =
         testScope.runTest {
             setAuthMethod(AuthenticationMethodModel.Password)
-            val upDestinationSceneKey by
-                collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+            val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+            val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
             emulateUserDrivenTransition(
                 to = upDestinationSceneKey,
@@ -487,8 +486,8 @@
     fun bouncerActionButtonClick_opensEmergencyServicesDialer() =
         testScope.runTest {
             setAuthMethod(AuthenticationMethodModel.Password)
-            val upDestinationSceneKey by
-                collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+            val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+            val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
             emulateUserDrivenTransition(to = upDestinationSceneKey)
 
@@ -507,8 +506,8 @@
         testScope.runTest {
             setAuthMethod(AuthenticationMethodModel.Password)
             startPhoneCall()
-            val upDestinationSceneKey by
-                collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+            val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+            val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
             emulateUserDrivenTransition(to = upDestinationSceneKey)
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
index d3fa360..cd79ed1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
@@ -54,7 +54,7 @@
     private val kosmos = Kosmos()
     private val testScope = kosmos.testScope
     private val sceneInteractor = kosmos.sceneInteractor
-    private val deviceEntryInteractor = kosmos.deviceEntryInteractor
+    private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor }
 
     private lateinit var shadeInteractor: ShadeInteractor
     private lateinit var underTest: ShadeControllerSceneImpl
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 e683f34c..278bf9b 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
@@ -38,6 +38,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
 import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
+import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
 import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
 import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel
 import com.android.systemui.kosmos.testScope
@@ -699,6 +700,25 @@
         }
 
     @Test
+    fun alphaOnFullQsExpansion() =
+        testScope.runTest {
+            val viewState = ViewStateAccessor()
+            val alpha by collectLastValue(underTest.keyguardAlpha(viewState))
+
+            showLockscreenWithQSExpanded()
+
+            // Alpha fades out as QS expands
+            shadeRepository.setQsExpansion(0.5f)
+            assertThat(alpha).isWithin(0.01f).of(0.5f)
+            shadeRepository.setQsExpansion(0.9f)
+            assertThat(alpha).isWithin(0.01f).of(0.1f)
+
+            // Ensure that alpha is set back to 1f when QS is fully expanded
+            shadeRepository.setQsExpansion(1f)
+            assertThat(alpha).isEqualTo(1f)
+        }
+
+    @Test
     fun shadeCollapseFadeIn() =
         testScope.runTest {
             val fadeIn by collectLastValue(underTest.shadeCollapseFadeIn)
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 a2f3ccb..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
@@ -113,7 +113,29 @@
     }
 
     @Test
-    fun streamIsMuted_getStream_volumeZero() {
+    fun zenMuted_cantChange() {
+        with(kosmos) {
+            testScope.runTest {
+                notificationsSoundPolicyRepository.updateNotificationPolicy()
+                notificationsSoundPolicyRepository.updateZenMode(
+                    ZenMode(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS)
+                )
+
+                val canChangeVolume by
+                    collectLastValue(
+                        underTest.canChangeVolume(AudioStream(AudioManager.STREAM_NOTIFICATION))
+                    )
+
+                underTest.setMuted(AudioStream(AudioManager.STREAM_RING), true)
+                runCurrent()
+
+                assertThat(canChangeVolume).isFalse()
+            }
+        }
+    }
+
+    @Test
+    fun streamIsMuted_getStream_volumeMin() {
         with(kosmos) {
             testScope.runTest {
                 val model by collectLastValue(underTest.getAudioStream(audioStream))
@@ -144,7 +166,7 @@
     }
 
     @Test
-    fun ringerModeVibrateAndMuted_getNotificationStream_volumeIsZero() {
+    fun ringerModeVibrateAndMuted_getNotificationStream_volumeIsMin() {
         with(kosmos) {
             testScope.runTest {
                 audioRepository.setRingerMode(RingerMode(AudioManager.RINGER_MODE_VIBRATE))
@@ -162,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/interactor/SpatialAudioComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
index 06ae220..7c6ab9d 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
@@ -108,6 +108,7 @@
 
                 assertThat(values)
                     .containsExactly(
+                        SpatialAudioEnabledModel.Unknown,
                         SpatialAudioEnabledModel.Disabled,
                         SpatialAudioEnabledModel.HeadTrackingEnabled,
                         SpatialAudioEnabledModel.SpatialAudioEnabled,
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-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index f51e109..7341015 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -304,6 +304,15 @@
     <!-- An explanation text that the password needs to be entered since the user hasn't used strong authentication since quite some time. [CHAR LIMIT=80] -->
     <string name="kg_prompt_reason_timeout_password">For additional security, use password instead</string>
 
+    <!-- An explanation text that the pin needs to be provided to enter the device for security reasons. [CHAR LIMIT=70] -->
+    <string name="kg_prompt_added_security_pin">PIN required for additional security</string>
+
+    <!-- An explanation text that the pattern needs to be provided to enter the device for security reasons. [CHAR LIMIT=70] -->
+    <string name="kg_prompt_added_security_pattern">Pattern required for additional security</string>
+
+    <!-- An explanation text that the password needs to be provided to enter the device for security reasons. [CHAR LIMIT=70] -->
+    <string name="kg_prompt_added_security_password">Password required for additional security</string>
+
     <!-- An explanation text that the credential needs to be entered because a device admin has
     locked the device. [CHAR LIMIT=80] -->
     <string name="kg_prompt_reason_device_admin">Device locked by admin</string>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 25596cc..e3a5e15 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1536,8 +1536,12 @@
 
     <!-- Media device casting volume slider label [CHAR_LIMIT=20] -->
     <string name="media_device_cast">Cast</string>
-    <!-- A message shown when the notification volume changing is disabled because of the muted ring stream [CHAR_LIMIT=40]-->
+    <!-- A message shown when the notification volume changing is disabled because of the muted ring stream [CHAR_LIMIT=50]-->
     <string name="stream_notification_unavailable">Unavailable because ring is muted</string>
+    <!-- A message shown when the alarm volume changing is disabled because of the don't disturb mode [CHAR_LIMIT=50]-->
+    <string name="stream_alarm_unavailable">Unavailable because Do Not Disturb is on</string>
+    <!-- A message shown when the media volume changing is disabled because of the don't disturb mode [CHAR_LIMIT=50]-->
+    <string name="stream_media_unavailable">Unavailable because Do Not Disturb is on</string>
 
     <!-- Shown in the header of quick settings to indicate to the user that their phone ringer is on vibrate. [CHAR_LIMIT=NONE] -->
     <!-- Shown in the header of quick settings to indicate to the user that their phone ringer is on silent (muted). [CHAR_LIMIT=NONE] -->
@@ -1552,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>
@@ -1572,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/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 4e7809a..59516be 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -965,6 +965,10 @@
         <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
     </style>
 
+    <style name="Widget.SliceView.VolumePanel">
+        <item name="hideHeaderRow">true</item>
+    </style>
+
     <style name="Theme.VolumePanelActivity.Popup" parent="@style/Theme.SystemUI.Dialog">
         <item name="android:dialogCornerRadius">44dp</item>
         <item name="android:colorBackground">?androidprv:attr/materialColorSurfaceContainerHigh
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 6e611fe..42ba05c 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -35,6 +35,9 @@
     srcs: [
         ":statslog-SystemUI-java-gen",
     ],
+    libs: [
+        "androidx.annotation_annotation",
+    ],
 }
 
 android_library {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 84c8ea7..26e91b6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -122,7 +122,7 @@
             case PROMPT_REASON_USER_REQUEST:
                 return R.string.kg_prompt_after_user_lockdown_password;
             case PROMPT_REASON_PREPARE_FOR_UPDATE:
-                return R.string.kg_prompt_reason_timeout_password;
+                return R.string.kg_prompt_added_security_password;
             case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
                 return R.string.kg_prompt_reason_timeout_password;
             case PROMPT_REASON_TRUSTAGENT_EXPIRED:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index bf8900d..caa74780 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -323,7 +323,7 @@
                 resId = R.string.kg_prompt_after_user_lockdown_pattern;
                 break;
             case PROMPT_REASON_PREPARE_FOR_UPDATE:
-                resId = R.string.kg_prompt_reason_timeout_pattern;
+                resId = R.string.kg_prompt_added_security_pattern;
                 break;
             case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
                 resId = R.string.kg_prompt_reason_timeout_pattern;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index bcab6f0..fbe9edf 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -134,7 +134,7 @@
             case PROMPT_REASON_USER_REQUEST:
                 return R.string.kg_prompt_after_user_lockdown_pin;
             case PROMPT_REASON_PREPARE_FOR_UPDATE:
-                return R.string.kg_prompt_reason_timeout_pin;
+                return R.string.kg_prompt_added_security_pin;
             case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
                 return R.string.kg_prompt_reason_timeout_pin;
             case PROMPT_REASON_TRUSTAGENT_EXPIRED:
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..270fedc 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
@@ -101,6 +101,10 @@
         loadLayoutResources();
 
         addView(mTargetFeaturesView);
+
+        setClickable(false);
+        setFocusable(false);
+        setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
     }
 
     @Override
@@ -224,8 +228,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 +334,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();
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..97e38f4 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -319,6 +319,9 @@
         if (Flags.floatingMenuAnimatedTuck()) {
             setClipChildren(true);
         }
+        setClickable(false);
+        setFocusable(false);
+        setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
     }
 
     @Override
@@ -443,21 +446,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 +475,7 @@
                 setClipBounds(null);
             }
         }
-        if (Flags.floatingMenuImeDisplacementAnimation()) {
-            mMenuView.onArrivalAtPosition(false);
-        }
+        mMenuView.onArrivalAtPosition(false);
     }
 
     void dispatchAccessibilityAction(int id) {
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/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 9de71c1..8bd675c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -881,7 +881,7 @@
 
         final Runnable endActionRunnable = () -> {
             setVisibility(View.INVISIBLE);
-            if (Flags.customBiometricPrompt()) {
+            if (Flags.customBiometricPrompt() && constraintBp()) {
                 mPromptSelectorInteractorProvider.get().resetPrompt();
             }
             removeWindowIfAttached();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
index b87fadf..4d88f49 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
@@ -18,6 +18,7 @@
 
 import android.hardware.biometrics.Flags
 import android.hardware.biometrics.PromptInfo
+import com.android.systemui.Flags.constraintBp
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.biometrics.Utils
 import com.android.systemui.biometrics.Utils.isDeviceCredentialAllowed
@@ -151,6 +152,7 @@
         val hasCredentialViewShown = kind.value !is PromptKind.Biometric
         val showBpForCredential =
             Flags.customBiometricPrompt() &&
+                constraintBp() &&
                 !Utils.isBiometricAllowed(promptInfo) &&
                 isDeviceCredentialAllowed(promptInfo) &&
                 promptInfo.contentView != null
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index e48f05d..7bb75bf 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -22,6 +22,7 @@
 import android.hardware.biometrics.BiometricAuthenticator
 import android.hardware.biometrics.BiometricConstants
 import android.hardware.biometrics.BiometricPrompt
+import android.hardware.biometrics.Flags
 import android.hardware.face.FaceManager
 import android.text.method.ScrollingMovementMethod
 import android.util.Log
@@ -166,11 +167,14 @@
             titleView.text = viewModel.title.first()
             subtitleView.text = viewModel.subtitle.first()
             descriptionView.text = viewModel.description.first()
-            BiometricCustomizedViewBinder.bind(
-                customizedViewContainer,
-                view.requireViewById(R.id.space_above_content),
-                viewModel
-            )
+
+            if (Flags.customBiometricPrompt() && constraintBp()) {
+                BiometricCustomizedViewBinder.bind(
+                    customizedViewContainer,
+                    view.requireViewById(R.id.space_above_content),
+                    viewModel
+                )
+            }
 
             // set button listeners
             negativeButton.setOnClickListener { legacyCallback.onButtonNegative() }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
index 3469cfa..e457601 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
@@ -86,7 +86,12 @@
                 launch {
                     var width = 0
                     var height = 0
-                    viewModel.activeAuthType.collect { activeAuthType ->
+                    combine(promptViewModel.size, viewModel.activeAuthType, ::Pair).collect {
+                        (_, activeAuthType) ->
+                        // Every time after bp shows, [isIconViewLoaded] is set to false in
+                        // [BiometricViewSizeBinder]. Then when biometric prompt view is redrew
+                        // (when size or activeAuthType changes), we need to update
+                        // [isIconViewLoaded] here to keep it correct.
                         when (activeAuthType) {
                             AuthType.Fingerprint,
                             AuthType.Coex -> {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
index 9c28f1c..9949e4c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
@@ -141,6 +141,9 @@
     /** Hide the side fingerprint sensor indicator */
     private fun hide() {
         if (overlayView != null) {
+            val lottie = overlayView!!.requireViewById<LottieAnimationView>(R.id.sidefps_animation)
+            lottie.pauseAnimation()
+            lottie.removeAllLottieOnCompositionLoadedListener()
             windowManager.get().removeView(overlayView)
             overlayView = null
         }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 61aeffe..86b0b44 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -29,6 +29,7 @@
 import android.view.HapticFeedbackConstants
 import android.view.MotionEvent
 import com.android.systemui.Flags.bpTalkback
+import com.android.systemui.Flags.constraintBp
 import com.android.systemui.biometrics.UdfpsUtils
 import com.android.systemui.biometrics.Utils
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
@@ -217,18 +218,12 @@
      */
     val faceMode: Flow<Boolean> =
         combine(modalities, isConfirmationRequired, fingerprintStartMode) {
-                modalities: BiometricModalities,
-                isConfirmationRequired: Boolean,
-                fingerprintStartMode: FingerprintStartMode ->
-                if (modalities.hasFaceAndFingerprint) {
-                    if (isConfirmationRequired) {
-                        false
-                    } else {
-                        !fingerprintStartMode.isStarted
-                    }
-                } else {
-                    false
-                }
+                modalities,
+                isConfirmationRequired,
+                fingerprintStartMode ->
+                modalities.hasFaceAndFingerprint &&
+                    !isConfirmationRequired &&
+                    fingerprintStartMode == FingerprintStartMode.Pending
             }
             .distinctUntilChanged()
 
@@ -248,14 +243,11 @@
      * asset to be loaded before determining the prompt size.
      */
     val isIconViewLoaded: Flow<Boolean> =
-        combine(credentialKind, _isIconViewLoaded.asStateFlow()) { credentialKind, isIconViewLoaded
-            ->
-            if (credentialKind is PromptKind.Biometric) {
-                isIconViewLoaded
-            } else {
-                true
+        combine(modalities, _isIconViewLoaded.asStateFlow()) { modalities, isIconViewLoaded ->
+                val noIcon = modalities.isEmpty
+                noIcon || isIconViewLoaded
             }
-        }
+            .distinctUntilChanged()
 
     // Sets whether the prompt's iconView animation has been loaded in the view yet.
     fun setIsIconViewLoaded(iconViewLoaded: Boolean) {
@@ -284,7 +276,7 @@
         promptSelectorInteractor.prompt
             .map {
                 when {
-                    !customBiometricPrompt() || it == null -> null
+                    !(customBiometricPrompt() && constraintBp()) || it == null -> null
                     it.logoRes != -1 -> context.resources.getDrawable(it.logoRes, context.theme)
                     it.logoBitmap != null -> BitmapDrawable(context.resources, it.logoBitmap)
                     else ->
@@ -304,7 +296,7 @@
         promptSelectorInteractor.prompt
             .map {
                 when {
-                    !customBiometricPrompt() || it == null -> ""
+                    !(customBiometricPrompt() && constraintBp()) || it == null -> ""
                     it.logoDescription != null -> it.logoDescription
                     else ->
                         try {
@@ -329,7 +321,7 @@
     /** Custom content view for the prompt. */
     val contentView: Flow<PromptContentView?> =
         promptSelectorInteractor.prompt
-            .map { if (customBiometricPrompt()) it?.contentView else null }
+            .map { if (customBiometricPrompt() && constraintBp()) it?.contentView else null }
             .distinctUntilChanged()
 
     private val originalDescription =
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/BouncerMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
index c25e748..7f6fc91 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
@@ -23,10 +23,12 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.Flags
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.biometrics.data.repository.FacePropertyRepository
 import com.android.systemui.biometrics.shared.model.SensorStrength
 import com.android.systemui.bouncer.data.repository.BouncerMessageRepository
 import com.android.systemui.bouncer.shared.model.BouncerMessageModel
+import com.android.systemui.bouncer.shared.model.BouncerMessageStrings
 import com.android.systemui.bouncer.shared.model.Message
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -35,46 +37,6 @@
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.res.R.string.bouncer_face_not_recognized
-import com.android.systemui.res.R.string.keyguard_enter_password
-import com.android.systemui.res.R.string.keyguard_enter_pattern
-import com.android.systemui.res.R.string.keyguard_enter_pin
-import com.android.systemui.res.R.string.kg_bio_too_many_attempts_password
-import com.android.systemui.res.R.string.kg_bio_too_many_attempts_pattern
-import com.android.systemui.res.R.string.kg_bio_too_many_attempts_pin
-import com.android.systemui.res.R.string.kg_bio_try_again_or_password
-import com.android.systemui.res.R.string.kg_bio_try_again_or_pattern
-import com.android.systemui.res.R.string.kg_bio_try_again_or_pin
-import com.android.systemui.res.R.string.kg_face_locked_out
-import com.android.systemui.res.R.string.kg_fp_not_recognized
-import com.android.systemui.res.R.string.kg_primary_auth_locked_out_password
-import com.android.systemui.res.R.string.kg_primary_auth_locked_out_pattern
-import com.android.systemui.res.R.string.kg_primary_auth_locked_out_pin
-import com.android.systemui.res.R.string.kg_prompt_after_adaptive_auth_lock
-import com.android.systemui.res.R.string.kg_prompt_after_dpm_lock
-import com.android.systemui.res.R.string.kg_prompt_after_update_password
-import com.android.systemui.res.R.string.kg_prompt_after_update_pattern
-import com.android.systemui.res.R.string.kg_prompt_after_update_pin
-import com.android.systemui.res.R.string.kg_prompt_after_user_lockdown_password
-import com.android.systemui.res.R.string.kg_prompt_after_user_lockdown_pattern
-import com.android.systemui.res.R.string.kg_prompt_after_user_lockdown_pin
-import com.android.systemui.res.R.string.kg_prompt_auth_timeout
-import com.android.systemui.res.R.string.kg_prompt_password_auth_timeout
-import com.android.systemui.res.R.string.kg_prompt_pattern_auth_timeout
-import com.android.systemui.res.R.string.kg_prompt_pin_auth_timeout
-import com.android.systemui.res.R.string.kg_prompt_reason_restart_password
-import com.android.systemui.res.R.string.kg_prompt_reason_restart_pattern
-import com.android.systemui.res.R.string.kg_prompt_reason_restart_pin
-import com.android.systemui.res.R.string.kg_prompt_unattended_update
-import com.android.systemui.res.R.string.kg_too_many_failed_attempts_countdown
-import com.android.systemui.res.R.string.kg_trust_agent_disabled
-import com.android.systemui.res.R.string.kg_unlock_with_password_or_fp
-import com.android.systemui.res.R.string.kg_unlock_with_pattern_or_fp
-import com.android.systemui.res.R.string.kg_unlock_with_pin_or_fp
-import com.android.systemui.res.R.string.kg_wrong_input_try_fp_suggestion
-import com.android.systemui.res.R.string.kg_wrong_password_try_again
-import com.android.systemui.res.R.string.kg_wrong_pattern_try_again
-import com.android.systemui.res.R.string.kg_wrong_pin_try_again
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.kotlin.Quint
 import javax.inject.Inject
@@ -130,17 +92,22 @@
                 repository.setMessage(
                     when (biometricSourceType) {
                         BiometricSourceType.FINGERPRINT ->
-                            incorrectFingerprintInput(currentSecurityMode)
+                            BouncerMessageStrings.incorrectFingerprintInput(
+                                    currentSecurityMode.toAuthModel()
+                                )
+                                .toMessage()
                         BiometricSourceType.FACE ->
-                            incorrectFaceInput(
-                                currentSecurityMode,
-                                isFingerprintAuthCurrentlyAllowed.value
-                            )
+                            BouncerMessageStrings.incorrectFaceInput(
+                                    currentSecurityMode.toAuthModel(),
+                                    isFingerprintAuthCurrentlyAllowed.value
+                                )
+                                .toMessage()
                         else ->
-                            defaultMessage(
-                                currentSecurityMode,
-                                isFingerprintAuthCurrentlyAllowed.value
-                            )
+                            BouncerMessageStrings.defaultMessage(
+                                    currentSecurityMode.toAuthModel(),
+                                    isFingerprintAuthCurrentlyAllowed.value
+                                )
+                                .toMessage()
                     }
                 )
             }
@@ -189,45 +156,79 @@
                     trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterReboot
                 ) {
                     if (wasRebootedForMainlineUpdate) {
-                        authRequiredForMainlineUpdate(currentSecurityMode)
+                        BouncerMessageStrings.authRequiredForMainlineUpdate(
+                                currentSecurityMode.toAuthModel()
+                            )
+                            .toMessage()
                     } else {
-                        authRequiredAfterReboot(currentSecurityMode)
+                        BouncerMessageStrings.authRequiredAfterReboot(
+                                currentSecurityMode.toAuthModel()
+                            )
+                            .toMessage()
                     }
                 } else if (trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterTimeout) {
-                    authRequiredAfterPrimaryAuthTimeout(currentSecurityMode)
+                    BouncerMessageStrings.authRequiredAfterPrimaryAuthTimeout(
+                            currentSecurityMode.toAuthModel()
+                        )
+                        .toMessage()
                 } else if (flags.isPrimaryAuthRequiredAfterDpmLockdown) {
-                    authRequiredAfterAdminLockdown(currentSecurityMode)
+                    BouncerMessageStrings.authRequiredAfterAdminLockdown(
+                            currentSecurityMode.toAuthModel()
+                        )
+                        .toMessage()
                 } else if (
-                    trustOrBiometricsAvailable && flags.primaryAuthRequiredForUnattendedUpdate
+                    trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredForUnattendedUpdate
                 ) {
-                    authRequiredForUnattendedUpdate(currentSecurityMode)
+                    BouncerMessageStrings.authRequiredForUnattendedUpdate(
+                            currentSecurityMode.toAuthModel()
+                        )
+                        .toMessage()
                 } else if (fpLockedOut) {
-                    class3AuthLockedOut(currentSecurityMode)
+                    BouncerMessageStrings.class3AuthLockedOut(currentSecurityMode.toAuthModel())
+                        .toMessage()
                 } else if (faceLockedOut) {
                     if (isFaceAuthClass3) {
-                        class3AuthLockedOut(currentSecurityMode)
+                        BouncerMessageStrings.class3AuthLockedOut(currentSecurityMode.toAuthModel())
+                            .toMessage()
                     } else {
-                        faceLockedOut(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
+                        BouncerMessageStrings.faceLockedOut(
+                                currentSecurityMode.toAuthModel(),
+                                isFingerprintAuthCurrentlyAllowed.value
+                            )
+                            .toMessage()
                     }
                 } else if (flags.isSomeAuthRequiredAfterAdaptiveAuthRequest) {
-                    authRequiredAfterAdaptiveAuthRequest(
-                        currentSecurityMode,
-                        isFingerprintAuthCurrentlyAllowed.value
-                    )
+                    BouncerMessageStrings.authRequiredAfterAdaptiveAuthRequest(
+                            currentSecurityMode.toAuthModel(),
+                            isFingerprintAuthCurrentlyAllowed.value
+                        )
+                        .toMessage()
                 } else if (
                     trustOrBiometricsAvailable &&
                         flags.strongerAuthRequiredAfterNonStrongBiometricsTimeout
                 ) {
-                    nonStrongAuthTimeout(
-                        currentSecurityMode,
-                        isFingerprintAuthCurrentlyAllowed.value
-                    )
+                    BouncerMessageStrings.nonStrongAuthTimeout(
+                            currentSecurityMode.toAuthModel(),
+                            isFingerprintAuthCurrentlyAllowed.value
+                        )
+                        .toMessage()
                 } else if (isTrustUsuallyManaged && flags.someAuthRequiredAfterUserRequest) {
-                    trustAgentDisabled(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
+                    BouncerMessageStrings.trustAgentDisabled(
+                            currentSecurityMode.toAuthModel(),
+                            isFingerprintAuthCurrentlyAllowed.value
+                        )
+                        .toMessage()
                 } else if (isTrustUsuallyManaged && flags.someAuthRequiredAfterTrustAgentExpired) {
-                    trustAgentDisabled(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
+                    BouncerMessageStrings.trustAgentDisabled(
+                            currentSecurityMode.toAuthModel(),
+                            isFingerprintAuthCurrentlyAllowed.value
+                        )
+                        .toMessage()
                 } else if (trustOrBiometricsAvailable && flags.isInUserLockdown) {
-                    authRequiredAfterUserLockdown(currentSecurityMode)
+                    BouncerMessageStrings.authRequiredAfterUserLockdown(
+                            currentSecurityMode.toAuthModel()
+                        )
+                        .toMessage()
                 } else {
                     defaultMessage
                 }
@@ -244,7 +245,11 @@
 
                 override fun onTick(millisUntilFinished: Long) {
                     val secondsRemaining = (millisUntilFinished / 1000.0).roundToInt()
-                    val message = primaryAuthLockedOut(currentSecurityMode)
+                    val message =
+                        BouncerMessageStrings.primaryAuthLockedOut(
+                                currentSecurityMode.toAuthModel()
+                            )
+                            .toMessage()
                     message.message?.animate = false
                     message.message?.formatterArgs =
                         mutableMapOf<String, Any>(Pair("count", secondsRemaining))
@@ -258,7 +263,11 @@
         if (!Flags.revampedBouncerMessages()) return
 
         repository.setMessage(
-            incorrectSecurityInput(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
+            BouncerMessageStrings.incorrectSecurityInput(
+                    currentSecurityMode.toAuthModel(),
+                    isFingerprintAuthCurrentlyAllowed.value
+                )
+                .toMessage()
         )
     }
 
@@ -285,7 +294,12 @@
     }
 
     private val defaultMessage: BouncerMessageModel
-        get() = defaultMessage(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
+        get() =
+            BouncerMessageStrings.defaultMessage(
+                    currentSecurityMode.toAuthModel(),
+                    isFingerprintAuthCurrentlyAllowed.value
+                )
+                .toMessage()
 
     fun onPrimaryBouncerUserInput() {
         if (!Flags.revampedBouncerMessages()) return
@@ -354,283 +368,35 @@
     return BouncerMessageModel(
         message =
             Message(
-                messageResId = defaultMessage(securityMode, fpAuthIsAllowed).message?.messageResId,
+                messageResId =
+                    BouncerMessageStrings.defaultMessage(
+                            securityMode.toAuthModel(),
+                            fpAuthIsAllowed
+                        )
+                        .toMessage()
+                        .message
+                        ?.messageResId,
                 animate = false
             ),
         secondaryMessage = Message(message = secondaryMessage, animate = false)
     )
 }
 
-private fun defaultMessage(
-    securityMode: SecurityMode,
-    fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
-    return if (fpAuthIsAllowed) {
-        defaultMessageWithFingerprint(securityMode)
-    } else
-        when (securityMode) {
-            SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-            SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-            SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-            else -> Pair(0, 0)
-        }.toMessage()
-}
-
-private fun defaultMessageWithFingerprint(securityMode: SecurityMode): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, 0)
-        SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, 0)
-        SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, 0)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
-private fun incorrectSecurityInput(
-    securityMode: SecurityMode,
-    fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
-    return if (fpAuthIsAllowed) {
-        incorrectSecurityInputWithFingerprint(securityMode)
-    } else
-        when (securityMode) {
-            SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, 0)
-            SecurityMode.Password -> Pair(kg_wrong_password_try_again, 0)
-            SecurityMode.PIN -> Pair(kg_wrong_pin_try_again, 0)
-            else -> Pair(0, 0)
-        }.toMessage()
-}
-
-private fun incorrectSecurityInputWithFingerprint(securityMode: SecurityMode): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, kg_wrong_input_try_fp_suggestion)
-        SecurityMode.Password -> Pair(kg_wrong_password_try_again, kg_wrong_input_try_fp_suggestion)
-        SecurityMode.PIN -> Pair(kg_wrong_pin_try_again, kg_wrong_input_try_fp_suggestion)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
-private fun incorrectFingerprintInput(securityMode: SecurityMode): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_pattern)
-        SecurityMode.Password -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_password)
-        SecurityMode.PIN -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_pin)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
-private fun incorrectFaceInput(
-    securityMode: SecurityMode,
-    fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
-    return if (fpAuthIsAllowed) incorrectFaceInputWithFingerprintAllowed(securityMode)
-    else
-        when (securityMode) {
-            SecurityMode.Pattern -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_pattern)
-            SecurityMode.Password -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_password)
-            SecurityMode.PIN -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_pin)
-            else -> Pair(0, 0)
-        }.toMessage()
-}
-
-private fun incorrectFaceInputWithFingerprintAllowed(
-    securityMode: SecurityMode
-): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, bouncer_face_not_recognized)
-        SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, bouncer_face_not_recognized)
-        SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, bouncer_face_not_recognized)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
-private fun biometricLockout(securityMode: SecurityMode): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_bio_too_many_attempts_pattern)
-        SecurityMode.Password -> Pair(keyguard_enter_password, kg_bio_too_many_attempts_password)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_bio_too_many_attempts_pin)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
-private fun authRequiredAfterReboot(securityMode: SecurityMode): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_reason_restart_pattern)
-        SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_reason_restart_password)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
-private fun authRequiredAfterAdminLockdown(securityMode: SecurityMode): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_dpm_lock)
-        SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_after_dpm_lock)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
-private fun authRequiredAfterAdaptiveAuthRequest(
-    securityMode: SecurityMode,
-    fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
-    return if (fpAuthIsAllowed) authRequiredAfterAdaptiveAuthRequestFingerprintAllowed(securityMode)
-    else
-        return when (securityMode) {
-            SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_adaptive_auth_lock)
-            SecurityMode.Password ->
-                Pair(keyguard_enter_password, kg_prompt_after_adaptive_auth_lock)
-            SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_adaptive_auth_lock)
-            else -> Pair(0, 0)
-        }.toMessage()
-}
-
-private fun authRequiredAfterAdaptiveAuthRequestFingerprintAllowed(
-    securityMode: SecurityMode
-): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern ->
-            Pair(kg_unlock_with_pattern_or_fp, kg_prompt_after_adaptive_auth_lock)
-        SecurityMode.Password ->
-            Pair(kg_unlock_with_password_or_fp, kg_prompt_after_adaptive_auth_lock)
-        SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_prompt_after_adaptive_auth_lock)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
-private fun authRequiredAfterUserLockdown(securityMode: SecurityMode): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_user_lockdown_pattern)
-        SecurityMode.Password ->
-            Pair(keyguard_enter_password, kg_prompt_after_user_lockdown_password)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
-private fun authRequiredForUnattendedUpdate(securityMode: SecurityMode): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_unattended_update)
-        SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_unattended_update)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_unattended_update)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
-private fun authRequiredForMainlineUpdate(securityMode: SecurityMode): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_update_pattern)
-        SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_after_update_password)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_update_pin)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
-private fun authRequiredAfterPrimaryAuthTimeout(securityMode: SecurityMode): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_pattern_auth_timeout)
-        SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_password_auth_timeout)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
-private fun nonStrongAuthTimeout(
-    securityMode: SecurityMode,
-    fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
-    return if (fpAuthIsAllowed) {
-        nonStrongAuthTimeoutWithFingerprintAllowed(securityMode)
-    } else
-        when (securityMode) {
-            SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_auth_timeout)
-            SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_auth_timeout)
-            SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_auth_timeout)
-            else -> Pair(0, 0)
-        }.toMessage()
-}
-
-fun nonStrongAuthTimeoutWithFingerprintAllowed(securityMode: SecurityMode): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_prompt_auth_timeout)
-        SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_prompt_auth_timeout)
-        SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_prompt_auth_timeout)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
-private fun faceLockedOut(
-    securityMode: SecurityMode,
-    fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
-    return if (fpAuthIsAllowed) faceLockedOutButFingerprintAvailable(securityMode)
-    else
-        when (securityMode) {
-            SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_face_locked_out)
-            SecurityMode.Password -> Pair(keyguard_enter_password, kg_face_locked_out)
-            SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_face_locked_out)
-            else -> Pair(0, 0)
-        }.toMessage()
-}
-
-private fun faceLockedOutButFingerprintAvailable(securityMode: SecurityMode): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_face_locked_out)
-        SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_face_locked_out)
-        SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_face_locked_out)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
-private fun class3AuthLockedOut(securityMode: SecurityMode): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_bio_too_many_attempts_pattern)
-        SecurityMode.Password -> Pair(keyguard_enter_password, kg_bio_too_many_attempts_password)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_bio_too_many_attempts_pin)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
-private fun trustAgentDisabled(
-    securityMode: SecurityMode,
-    fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
-    return if (fpAuthIsAllowed) trustAgentDisabledWithFingerprintAllowed(securityMode)
-    else
-        when (securityMode) {
-            SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_trust_agent_disabled)
-            SecurityMode.Password -> Pair(keyguard_enter_password, kg_trust_agent_disabled)
-            SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_trust_agent_disabled)
-            else -> Pair(0, 0)
-        }.toMessage()
-}
-
-private fun trustAgentDisabledWithFingerprintAllowed(
-    securityMode: SecurityMode
-): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_trust_agent_disabled)
-        SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_trust_agent_disabled)
-        SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_trust_agent_disabled)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
-private fun primaryAuthLockedOut(securityMode: SecurityMode): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern ->
-            Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_pattern)
-        SecurityMode.Password ->
-            Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_password)
-        SecurityMode.PIN ->
-            Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_pin)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
 private fun Pair<Int, Int>.toMessage(): BouncerMessageModel {
     return BouncerMessageModel(
         message = Message(messageResId = this.first, animate = false),
         secondaryMessage = Message(messageResId = this.second, animate = false)
     )
 }
+
+private fun SecurityMode.toAuthModel(): AuthenticationMethodModel {
+    return when (this) {
+        SecurityMode.Invalid -> AuthenticationMethodModel.None
+        SecurityMode.None -> AuthenticationMethodModel.None
+        SecurityMode.Pattern -> AuthenticationMethodModel.Pattern
+        SecurityMode.Password -> AuthenticationMethodModel.Password
+        SecurityMode.PIN -> AuthenticationMethodModel.Pin
+        SecurityMode.SimPin -> AuthenticationMethodModel.Sim
+        SecurityMode.SimPuk -> AuthenticationMethodModel.Sim
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt
index c3d4cb3..7d3075a 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt
@@ -44,6 +44,8 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
@@ -70,6 +72,9 @@
     val isLockedEsim: StateFlow<Boolean?> = repository.isLockedEsim
     val errorDialogMessage: StateFlow<String?> = repository.errorDialogMessage
 
+    private val _bouncerMessageChanged = MutableSharedFlow<String?>()
+    val bouncerMessageChanged: SharedFlow<String?> = _bouncerMessageChanged
+
     /** Returns the default message for the sim pin screen. */
     fun getDefaultMessage(): String {
         val isEsimLocked = repository.isLockedEsim.value ?: false
@@ -81,7 +86,7 @@
             return ""
         }
 
-        var count = telephonyManager.activeModemCount
+        val count = telephonyManager.activeModemCount
         val info: SubscriptionInfo? = repository.activeSubscriptionInfo.value
         val displayName = info?.displayName
         var msg: String =
@@ -156,32 +161,24 @@
         repository.setSimVerificationErrorMessage(null)
     }
 
-    /**
-     * Based on sim state, unlock the locked sim with the given credentials.
-     *
-     * @return Any message that should show associated with the provided input. Null means that no
-     *   message needs to be shown.
-     */
-    suspend fun verifySim(input: List<Any>): String? {
+    /** Based on sim state, unlock the locked sim with the given credentials. */
+    suspend fun verifySim(input: List<Any>) {
+        val code = input.joinToString(separator = "")
         if (repository.isSimPukLocked.value) {
-            return verifySimPuk(input.joinToString(separator = ""))
+            verifySimPuk(code)
+        } else {
+            verifySimPin(code)
         }
-
-        return verifySimPin(input.joinToString(separator = ""))
     }
 
-    /**
-     * Verifies the input and unlocks the locked sim with a 4-8 digit pin code.
-     *
-     * @return Any message that should show associated with the provided input. Null means that no
-     *   message needs to be shown.
-     */
-    private suspend fun verifySimPin(input: String): String? {
+    /** Verifies the input and unlocks the locked sim with a 4-8 digit pin code. */
+    private suspend fun verifySimPin(input: String) {
         val subscriptionId = repository.subscriptionId.value
         // A SIM PIN is 4 to 8 decimal digits according to
         // GSM 02.17 version 5.0.1, Section 5.6 PIN Management
         if (input.length < MIN_SIM_PIN_LENGTH || input.length > MAX_SIM_PIN_LENGTH) {
-            return resources.getString(R.string.kg_invalid_sim_pin_hint)
+            _bouncerMessageChanged.emit(resources.getString(R.string.kg_invalid_sim_pin_hint))
+            return
         }
         val result =
             withContext(backgroundDispatcher) {
@@ -190,8 +187,10 @@
                 telephonyManager.supplyIccLockPin(input)
             }
         when (result.result) {
-            PinResult.PIN_RESULT_TYPE_SUCCESS ->
+            PinResult.PIN_RESULT_TYPE_SUCCESS -> {
                 keyguardUpdateMonitor.reportSimUnlocked(subscriptionId)
+                _bouncerMessageChanged.emit(null)
+            }
             PinResult.PIN_RESULT_TYPE_INCORRECT -> {
                 if (result.attemptsRemaining <= CRITICAL_NUM_OF_ATTEMPTS) {
                     // Show a dialog to display the remaining number of attempts to verify the sim
@@ -199,24 +198,22 @@
                     repository.setSimVerificationErrorMessage(
                         getPinPasswordErrorMessage(result.attemptsRemaining)
                     )
+                    _bouncerMessageChanged.emit(null)
                 } else {
-                    return getPinPasswordErrorMessage(result.attemptsRemaining)
+                    _bouncerMessageChanged.emit(
+                        getPinPasswordErrorMessage(result.attemptsRemaining)
+                    )
                 }
             }
         }
-
-        return null
     }
 
     /**
      * Verifies the input and unlocks the locked sim with a puk code instead of pin.
      *
      * This occurs after incorrectly verifying the sim pin multiple times.
-     *
-     * @return Any message that should show associated with the provided input. Null means that no
-     *   message needs to be shown.
      */
-    private suspend fun verifySimPuk(entry: String): String? {
+    private suspend fun verifySimPuk(entry: String) {
         val (enteredSimPuk, enteredSimPin) = repository.simPukInputModel
         val subscriptionId: Int = repository.subscriptionId.value
 
@@ -224,10 +221,11 @@
         if (enteredSimPuk == null) {
             if (entry.length >= MIN_SIM_PUK_LENGTH) {
                 repository.setSimPukUserInput(enteredSimPuk = entry)
-                return resources.getString(R.string.kg_puk_enter_pin_hint)
+                _bouncerMessageChanged.emit(resources.getString(R.string.kg_puk_enter_pin_hint))
             } else {
-                return resources.getString(R.string.kg_invalid_sim_puk_hint)
+                _bouncerMessageChanged.emit(resources.getString(R.string.kg_invalid_sim_puk_hint))
             }
+            return
         }
 
         // Stage 2: Set a new sim pin to lock the sim card.
@@ -237,10 +235,11 @@
                     enteredSimPuk = enteredSimPuk,
                     enteredSimPin = entry,
                 )
-                return resources.getString(R.string.kg_enter_confirm_pin_hint)
+                _bouncerMessageChanged.emit(resources.getString(R.string.kg_enter_confirm_pin_hint))
             } else {
-                return resources.getString(R.string.kg_invalid_sim_pin_hint)
+                _bouncerMessageChanged.emit(resources.getString(R.string.kg_invalid_sim_pin_hint))
             }
+            return
         }
 
         // Stage 3: Confirm the newly set sim pin.
@@ -250,7 +249,8 @@
                 resources.getString(R.string.kg_invalid_confirm_pin_hint)
             )
             repository.setSimPukUserInput(enteredSimPuk = enteredSimPuk)
-            return resources.getString(R.string.kg_puk_enter_pin_hint)
+            _bouncerMessageChanged.emit(resources.getString(R.string.kg_puk_enter_pin_hint))
+            return
         }
 
         val result =
@@ -261,9 +261,11 @@
         resetSimPukUserInput()
 
         when (result.result) {
-            PinResult.PIN_RESULT_TYPE_SUCCESS ->
+            PinResult.PIN_RESULT_TYPE_SUCCESS -> {
                 keyguardUpdateMonitor.reportSimUnlocked(subscriptionId)
-            PinResult.PIN_RESULT_TYPE_INCORRECT ->
+                _bouncerMessageChanged.emit(null)
+            }
+            PinResult.PIN_RESULT_TYPE_INCORRECT -> {
                 if (result.attemptsRemaining <= CRITICAL_NUM_OF_ATTEMPTS) {
                     // Show a dialog to display the remaining number of attempts to verify the sim
                     // puk to the user.
@@ -274,17 +276,21 @@
                             isEsimLocked = repository.isLockedEsim.value == true
                         )
                     )
+                    _bouncerMessageChanged.emit(null)
                 } else {
-                    return getPukPasswordErrorMessage(
-                        result.attemptsRemaining,
-                        isDefault = false,
-                        isEsimLocked = repository.isLockedEsim.value == true
+                    _bouncerMessageChanged.emit(
+                        getPukPasswordErrorMessage(
+                            result.attemptsRemaining,
+                            isDefault = false,
+                            isEsimLocked = repository.isLockedEsim.value == true
+                        )
                     )
                 }
-            else -> return resources.getString(R.string.kg_password_puk_failed)
+            }
+            else -> {
+                _bouncerMessageChanged.emit(resources.getString(R.string.kg_password_puk_failed))
+            }
         }
-
-        return null
     }
 
     private fun getPinPasswordErrorMessage(attemptsRemaining: Int): String {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageStrings.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageStrings.kt
new file mode 100644
index 0000000..cb12ce5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageStrings.kt
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.shared.model
+
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Password
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin
+import com.android.systemui.res.R
+
+typealias BouncerMessagePair = Pair<Int, Int>
+
+val BouncerMessagePair.primaryMessage: Int
+    get() = this.first
+
+val BouncerMessagePair.secondaryMessage: Int
+    get() = this.second
+
+object BouncerMessageStrings {
+    private val EmptyMessage = Pair(0, 0)
+
+    fun defaultMessage(
+        securityMode: AuthenticationMethodModel,
+        fpAuthIsAllowed: Boolean
+    ): BouncerMessagePair {
+        return when (securityMode) {
+            Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), 0)
+            Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), 0)
+            Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), 0)
+            else -> EmptyMessage
+        }
+    }
+
+    fun incorrectSecurityInput(
+        securityMode: AuthenticationMethodModel,
+        fpAuthIsAllowed: Boolean
+    ): BouncerMessagePair {
+        val secondaryMessage = incorrectSecurityInputSecondaryMessage(fpAuthIsAllowed)
+        return when (securityMode) {
+            Pattern -> Pair(R.string.kg_wrong_pattern_try_again, secondaryMessage)
+            Password -> Pair(R.string.kg_wrong_password_try_again, secondaryMessage)
+            Pin -> Pair(R.string.kg_wrong_pin_try_again, secondaryMessage)
+            else -> EmptyMessage
+        }
+    }
+
+    private fun incorrectSecurityInputSecondaryMessage(fpAuthIsAllowed: Boolean): Int {
+        return if (fpAuthIsAllowed) R.string.kg_wrong_input_try_fp_suggestion else 0
+    }
+
+    fun incorrectFingerprintInput(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+        val primaryMessage = R.string.kg_fp_not_recognized
+        return when (securityMode) {
+            Pattern -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pattern)
+            Password -> Pair(primaryMessage, R.string.kg_bio_try_again_or_password)
+            Pin -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pin)
+            else -> EmptyMessage
+        }
+    }
+
+    fun incorrectFaceInput(
+        securityMode: AuthenticationMethodModel,
+        fpAuthIsAllowed: Boolean
+    ): BouncerMessagePair {
+        return if (fpAuthIsAllowed) incorrectFaceInputWithFingerprintAllowed(securityMode)
+        else {
+            val primaryMessage = R.string.bouncer_face_not_recognized
+            when (securityMode) {
+                Pattern -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pattern)
+                Password -> Pair(primaryMessage, R.string.kg_bio_try_again_or_password)
+                Pin -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pin)
+                else -> EmptyMessage
+            }
+        }
+    }
+
+    private fun incorrectFaceInputWithFingerprintAllowed(
+        securityMode: AuthenticationMethodModel
+    ): BouncerMessagePair {
+        val secondaryMsg = R.string.bouncer_face_not_recognized
+        return when (securityMode) {
+            Pattern -> Pair(patternDefaultMessage(true), secondaryMsg)
+            Password -> Pair(passwordDefaultMessage(true), secondaryMsg)
+            Pin -> Pair(pinDefaultMessage(true), secondaryMsg)
+            else -> EmptyMessage
+        }
+    }
+
+    fun authRequiredAfterReboot(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+        return when (securityMode) {
+            Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_reason_restart_pattern)
+            Password ->
+                Pair(passwordDefaultMessage(false), R.string.kg_prompt_reason_restart_password)
+            Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_reason_restart_pin)
+            else -> EmptyMessage
+        }
+    }
+
+    fun authRequiredAfterAdminLockdown(
+        securityMode: AuthenticationMethodModel
+    ): BouncerMessagePair {
+        val secondaryMsg = R.string.kg_prompt_after_dpm_lock
+        return when (securityMode) {
+            Pattern -> Pair(patternDefaultMessage(false), secondaryMsg)
+            Password -> Pair(passwordDefaultMessage(false), secondaryMsg)
+            Pin -> Pair(pinDefaultMessage(false), secondaryMsg)
+            else -> EmptyMessage
+        }
+    }
+
+    fun authRequiredAfterAdaptiveAuthRequest(
+        securityMode: AuthenticationMethodModel,
+        fpAuthIsAllowed: Boolean
+    ): BouncerMessagePair {
+        val secondaryMsg = R.string.kg_prompt_after_adaptive_auth_lock
+        return when (securityMode) {
+            Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+            Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+            Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+            else -> EmptyMessage
+        }
+    }
+
+    fun authRequiredAfterUserLockdown(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+        return when (securityMode) {
+            Pattern ->
+                Pair(patternDefaultMessage(false), R.string.kg_prompt_after_user_lockdown_pattern)
+            Password ->
+                Pair(passwordDefaultMessage(false), R.string.kg_prompt_after_user_lockdown_password)
+            Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_after_user_lockdown_pin)
+            else -> EmptyMessage
+        }
+    }
+
+    fun authRequiredForUnattendedUpdate(
+        securityMode: AuthenticationMethodModel
+    ): BouncerMessagePair {
+        return when (securityMode) {
+            Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_added_security_pattern)
+            Password ->
+                Pair(passwordDefaultMessage(false), R.string.kg_prompt_added_security_password)
+            Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_added_security_pin)
+            else -> EmptyMessage
+        }
+    }
+
+    fun authRequiredForMainlineUpdate(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+        return when (securityMode) {
+            Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_after_update_pattern)
+            Password ->
+                Pair(passwordDefaultMessage(false), R.string.kg_prompt_after_update_password)
+            Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_after_update_pin)
+            else -> EmptyMessage
+        }
+    }
+
+    fun authRequiredAfterPrimaryAuthTimeout(
+        securityMode: AuthenticationMethodModel
+    ): BouncerMessagePair {
+        return when (securityMode) {
+            Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_pattern_auth_timeout)
+            Password ->
+                Pair(passwordDefaultMessage(false), R.string.kg_prompt_password_auth_timeout)
+            Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_pin_auth_timeout)
+            else -> EmptyMessage
+        }
+    }
+
+    fun nonStrongAuthTimeout(
+        securityMode: AuthenticationMethodModel,
+        fpAuthIsAllowed: Boolean
+    ): BouncerMessagePair {
+        val secondaryMsg = R.string.kg_prompt_auth_timeout
+        return when (securityMode) {
+            Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+            Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+            Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+            else -> EmptyMessage
+        }
+    }
+
+    fun faceLockedOut(
+        securityMode: AuthenticationMethodModel,
+        fpAuthIsAllowed: Boolean
+    ): BouncerMessagePair {
+        val secondaryMsg = R.string.kg_face_locked_out
+        return when (securityMode) {
+            Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+            Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+            Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+            else -> EmptyMessage
+        }
+    }
+
+    fun class3AuthLockedOut(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+        return when (securityMode) {
+            Pattern -> Pair(patternDefaultMessage(false), R.string.kg_bio_too_many_attempts_pattern)
+            Password ->
+                Pair(passwordDefaultMessage(false), R.string.kg_bio_too_many_attempts_password)
+            Pin -> Pair(pinDefaultMessage(false), R.string.kg_bio_too_many_attempts_pin)
+            else -> EmptyMessage
+        }
+    }
+
+    fun trustAgentDisabled(
+        securityMode: AuthenticationMethodModel,
+        fpAuthIsAllowed: Boolean
+    ): BouncerMessagePair {
+        val secondaryMsg = R.string.kg_trust_agent_disabled
+        return when (securityMode) {
+            Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+            Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+            Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+            else -> EmptyMessage
+        }
+    }
+
+    fun primaryAuthLockedOut(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+        return when (securityMode) {
+            Pattern ->
+                Pair(
+                    R.string.kg_too_many_failed_attempts_countdown,
+                    R.string.kg_primary_auth_locked_out_pattern
+                )
+            Password ->
+                Pair(
+                    R.string.kg_too_many_failed_attempts_countdown,
+                    R.string.kg_primary_auth_locked_out_password
+                )
+            Pin ->
+                Pair(
+                    R.string.kg_too_many_failed_attempts_countdown,
+                    R.string.kg_primary_auth_locked_out_pin
+                )
+            else -> EmptyMessage
+        }
+    }
+
+    private fun patternDefaultMessage(fingerprintAllowed: Boolean): Int {
+        return if (fingerprintAllowed) R.string.kg_unlock_with_pattern_or_fp
+        else R.string.keyguard_enter_pattern
+    }
+
+    private fun pinDefaultMessage(fingerprintAllowed: Boolean): Int {
+        return if (fingerprintAllowed) R.string.kg_unlock_with_pin_or_fp
+        else R.string.keyguard_enter_pin
+    }
+
+    private fun passwordDefaultMessage(fingerprintAllowed: Boolean): Int {
+        return if (fingerprintAllowed) R.string.kg_unlock_with_password_or_fp
+        else R.string.keyguard_enter_password
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index 7f4a029..e910a92 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -157,8 +157,7 @@
         if (authenticationMethod == AuthenticationMethodModel.Sim) {
             viewModelScope.launch {
                 isSimUnlockingDialogVisible.value = true
-                val msg = simBouncerInteractor.verifySim(getInput())
-                interactor.setMessage(msg)
+                simBouncerInteractor.verifySim(getInput())
                 isSimUnlockingDialogVisible.value = false
                 clearInput()
             }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index 3819e61..4f4f3d0 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -97,6 +97,14 @@
                 }
             };
 
+    private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
+            new KeyguardStateController.Callback() {
+                @Override
+                public void onKeyguardShowingChanged() {
+                    updateSensorRegistration();
+                }
+            };
+
 
     private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
             new KeyguardUpdateMonitorCallback() {
@@ -176,6 +184,8 @@
         mStatusBarStateController.addCallback(mStatusBarStateListener);
         mState = mStatusBarStateController.getState();
 
+        mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
+
         mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
 
         mJavaAdapter.alwaysCollectFlow(
@@ -364,6 +374,24 @@
         } else {
             sessionEnd();
         }
+        updateSensorRegistration();
+    }
+
+    private boolean shouldBeRegisteredToSensors() {
+        return mScreenOn
+                && (mState == StatusBarState.KEYGUARD
+                || (mState == StatusBarState.SHADE
+                && mKeyguardStateController.isOccluded()
+                && mKeyguardStateController.isShowing()))
+                && !mShowingAod;
+    }
+
+    private void updateSensorRegistration() {
+        if (shouldBeRegisteredToSensors()) {
+            registerSensors();
+        } else {
+            unregisterSensors();
+        }
     }
 
     private void sessionStart() {
@@ -371,7 +399,6 @@
             logDebug("Starting Session");
             mSessionStarted = true;
             mFalsingDataProvider.setJustUnlockedWithFace(false);
-            registerSensors();
             mFalsingDataProvider.onSessionStarted();
         }
     }
@@ -380,7 +407,6 @@
         if (mSessionStarted) {
             logDebug("Ending Session");
             mSessionStarted = false;
-            unregisterSensors();
             mFalsingDataProvider.onSessionEnd();
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index c3c7411..98c8205 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.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.collectLatest
+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,43 @@
         //                }
         //            }
         //            .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,
+                    keyguardInteractor.isDreaming,
+                    // Emit a value on start so the combine starts.
+                    communalInteractor.userActivity.emitOnStart()
+                ) { scene, isDreaming, _ ->
+                    // Time out should run whenever we're dreaming and the hub is open, even if not
+                    // docked.
+                    scene == CommunalScenes.Communal && isDreaming
+                }
+                // collectLatest cancels the previous action block when new values arrive, so any
+                // already running timeout gets cancelled when conditions change or user interaction
+                // is detected.
+                .collectLatest { shouldTimeout ->
+                    if (!shouldTimeout) {
+                        return@collectLatest
+                    }
+                    delay(screenTimeout.milliseconds)
+                    communalInteractor.onSceneChanged(CommunalScenes.Blank)
+                }
+        }
     }
 
     private suspend fun determineSceneAfterTransition(
@@ -105,5 +154,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/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index a4011fd..1003050 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -69,6 +69,7 @@
 import com.android.systemui.toast.ToastModule;
 import com.android.systemui.unfold.SysUIUnfoldStartableModule;
 import com.android.systemui.unfold.UnfoldTransitionModule;
+import com.android.systemui.util.kotlin.SysUICoroutinesModule;
 import com.android.systemui.volume.dagger.VolumeModule;
 import com.android.systemui.wallpapers.dagger.WallpaperModule;
 
@@ -117,6 +118,7 @@
         ShadeModule.class,
         StartCentralSurfacesModule.class,
         SceneContainerFrameworkModule.class,
+        SysUICoroutinesModule.class,
         SysUIUnfoldStartableModule.class,
         UnfoldTransitionModule.Startables.class,
         ToastModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt
index 96171aa..d495fac 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt
@@ -18,6 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.keyguard.shared.model.AuthenticationFlags
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
@@ -31,9 +32,22 @@
 constructor(
     repository: BiometricSettingsRepository,
 ) {
+
+    /**
+     * Flags that control the device entry authentication behavior.
+     *
+     * This exposes why biometrics may not be currently allowed.
+     */
+    val authenticationFlags: Flow<AuthenticationFlags> = repository.authenticationFlags
+
+    /** Whether the current user has enrolled and enabled fingerprint auth. */
+    val isFingerprintAuthEnrolledAndEnabled: Flow<Boolean> =
+        repository.isFingerprintEnrolledAndEnabled
+
     val fingerprintAuthCurrentlyAllowed: Flow<Boolean> =
         repository.isFingerprintAuthCurrentlyAllowed
-    val faceAuthEnrolledAndEnabled: Flow<Boolean> = repository.isFaceAuthEnrolledAndEnabled
+    /** Whether the current user has enrolled and enabled face auth. */
+    val isFaceAuthEnrolledAndEnabled: Flow<Boolean> = repository.isFaceAuthEnrolledAndEnabled
     val faceAuthCurrentlyAllowed: Flow<Boolean> = repository.isFaceAuthCurrentlyAllowed
 
     /** Whether both fingerprint and face are enrolled and enabled for device entry. */
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
index 99bd25b..7733de4 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
@@ -32,6 +32,10 @@
     /** Current detection status */
     val detectionStatus: Flow<FaceDetectionStatus>
 
+    val lockedOut: Flow<Boolean>
+
+    val authenticated: Flow<Boolean>
+
     /** Can face auth be run right now */
     fun canFaceAuthRun(): Boolean
 
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
index a5f6f7c..8059993 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.deviceentry.domain.interactor
 
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
@@ -23,14 +25,20 @@
 import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
 import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.map
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class DeviceEntryFingerprintAuthInteractor
 @Inject
 constructor(
     repository: DeviceEntryFingerprintAuthRepository,
+    biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
+    fingerprintPropertyRepository: FingerprintPropertyRepository,
 ) {
     /** Whether fingerprint authentication is currently running or not */
     val isRunning: Flow<Boolean> = repository.isRunning
@@ -47,4 +55,21 @@
         repository.authenticationStatus.filterIsInstance<ErrorFingerprintAuthenticationStatus>()
     val fingerprintHelp: Flow<HelpFingerprintAuthenticationStatus> =
         repository.authenticationStatus.filterIsInstance<HelpFingerprintAuthenticationStatus>()
+
+    /**
+     * Whether fingerprint authentication is currently allowed for the user. This is true if the
+     * user has fingerprint auth enabled, enrolled, it is not disabled by any security timeouts by
+     * [com.android.systemui.keyguard.shared.model.AuthenticationFlags] and not locked out due to
+     * too many incorrect attempts.
+     */
+    val isFingerprintAuthCurrentlyAllowed: Flow<Boolean> =
+        combine(isLockedOut, biometricSettingsInteractor.fingerprintAuthCurrentlyAllowed, ::Pair)
+            .map { (lockedOut, currentlyAllowed) -> !lockedOut && currentlyAllowed }
+
+    /**
+     * Whether the fingerprint sensor is present under the display as opposed to being on the power
+     * button or behind/rear of the phone.
+     */
+    val isSensorUnderDisplay =
+        fingerprintPropertyRepository.sensorType.map(FingerprintSensorType::isUdfps)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 029a4f3..fa2421a 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -16,24 +16,30 @@
 
 package com.android.systemui.deviceentry.domain.interactor
 
+import androidx.annotation.VisibleForTesting
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
 import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
-import com.android.systemui.keyguard.data.repository.TrustRepository
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
+import com.android.systemui.flags.SystemPropertiesHelper
+import com.android.systemui.keyguard.domain.interactor.TrustInteractor
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.util.kotlin.Quad
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onStart
@@ -55,10 +61,13 @@
     private val repository: DeviceEntryRepository,
     private val authenticationInteractor: AuthenticationInteractor,
     private val sceneInteractor: SceneInteractor,
-    deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository,
-    trustRepository: TrustRepository,
+    faceAuthInteractor: DeviceEntryFaceAuthInteractor,
+    private val fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
+    private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
+    private val trustInteractor: TrustInteractor,
     flags: SceneContainerFlags,
     deviceUnlockedInteractor: DeviceUnlockedInteractor,
+    private val systemPropertiesHelper: SystemPropertiesHelper,
 ) {
     /**
      * Whether the device is unlocked.
@@ -96,8 +105,8 @@
      */
     private val isPassivelyAuthenticated =
         merge(
-                trustRepository.isCurrentUserTrusted,
-                deviceEntryFaceAuthRepository.isAuthenticated,
+                trustInteractor.isTrusted,
+                faceAuthInteractor.authenticated,
             )
             .onStart { emit(false) }
 
@@ -134,6 +143,67 @@
                 initialValue = null,
             )
 
+    private val faceEnrolledAndEnabled = biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled
+    private val fingerprintEnrolledAndEnabled =
+        biometricSettingsInteractor.isFingerprintAuthEnrolledAndEnabled
+    private val trustAgentEnabled = trustInteractor.isEnrolledAndEnabled
+
+    private val faceOrFingerprintOrTrustEnabled: Flow<Triple<Boolean, Boolean, Boolean>> =
+        combine(faceEnrolledAndEnabled, fingerprintEnrolledAndEnabled, trustAgentEnabled, ::Triple)
+
+    /**
+     * Reason why device entry is restricted to certain authentication methods for the current user.
+     *
+     * Emits null when there are no device entry restrictions active.
+     */
+    val deviceEntryRestrictionReason: Flow<DeviceEntryRestrictionReason?> =
+        faceOrFingerprintOrTrustEnabled.flatMapLatest {
+            (faceEnabled, fingerprintEnabled, trustEnabled) ->
+            if (faceEnabled || fingerprintEnabled || trustEnabled) {
+                combine(
+                        biometricSettingsInteractor.authenticationFlags,
+                        faceAuthInteractor.lockedOut,
+                        fingerprintAuthInteractor.isLockedOut,
+                        trustInteractor.isTrustAgentCurrentlyAllowed,
+                        ::Quad
+                    )
+                    .map { (authFlags, isFaceLockedOut, isFingerprintLockedOut, trustManaged) ->
+                        when {
+                            authFlags.isPrimaryAuthRequiredAfterReboot &&
+                                wasRebootedForMainlineUpdate ->
+                                DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate
+                            authFlags.isPrimaryAuthRequiredAfterReboot ->
+                                DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot
+                            authFlags.isPrimaryAuthRequiredAfterDpmLockdown ->
+                                DeviceEntryRestrictionReason.PolicyLockdown
+                            authFlags.isInUserLockdown -> DeviceEntryRestrictionReason.UserLockdown
+                            authFlags.isPrimaryAuthRequiredForUnattendedUpdate ->
+                                DeviceEntryRestrictionReason.UnattendedUpdate
+                            authFlags.isPrimaryAuthRequiredAfterTimeout ->
+                                DeviceEntryRestrictionReason.SecurityTimeout
+                            authFlags.isPrimaryAuthRequiredAfterLockout ->
+                                DeviceEntryRestrictionReason.BouncerLockedOut
+                            isFingerprintLockedOut ->
+                                DeviceEntryRestrictionReason.StrongBiometricsLockedOut
+                            isFaceLockedOut && faceAuthInteractor.isFaceAuthStrong() ->
+                                DeviceEntryRestrictionReason.StrongBiometricsLockedOut
+                            isFaceLockedOut -> DeviceEntryRestrictionReason.NonStrongFaceLockedOut
+                            authFlags.isSomeAuthRequiredAfterAdaptiveAuthRequest ->
+                                DeviceEntryRestrictionReason.AdaptiveAuthRequest
+                            (trustEnabled && !trustManaged) &&
+                                (authFlags.someAuthRequiredAfterTrustAgentExpired ||
+                                    authFlags.someAuthRequiredAfterUserRequest) ->
+                                DeviceEntryRestrictionReason.TrustAgentDisabled
+                            authFlags.strongerAuthRequiredAfterNonStrongBiometricsTimeout ->
+                                DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout
+                            else -> null
+                        }
+                    }
+            } else {
+                flowOf(null)
+            }
+        }
+
     /**
      * Attempt to enter the device and dismiss the lockscreen. If authentication is required to
      * unlock the device it will transition to bouncer.
@@ -187,4 +257,12 @@
             }
         }
     }
+
+    private val wasRebootedForMainlineUpdate
+        get() = systemPropertiesHelper.get(SYS_BOOT_REASON_PROP) == REBOOT_MAINLINE_UPDATE
+
+    companion object {
+        @VisibleForTesting const val SYS_BOOT_REASON_PROP = "sys.boot.reason.last"
+        @VisibleForTesting const val REBOOT_MAINLINE_UPDATE = "reboot,mainline_update"
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt
index fd6fbc9..98deda09 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt
@@ -77,7 +77,7 @@
 
     private fun startUpdatingFaceHelpMessageDeferral() {
         scope.launch {
-            biometricSettingsInteractor.faceAuthEnrolledAndEnabled
+            biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled
                 .flatMapLatest { faceEnrolledAndEnabled ->
                     if (faceEnrolledAndEnabled) {
                         faceAcquired
@@ -94,7 +94,7 @@
         }
 
         scope.launch {
-            biometricSettingsInteractor.faceAuthEnrolledAndEnabled
+            biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled
                 .flatMapLatest { faceEnrolledAndEnabled ->
                     if (faceEnrolledAndEnabled) {
                         faceHelp
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
index 3b94166..65f3eb7 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
@@ -31,10 +31,10 @@
  */
 @SysUISingleton
 class NoopDeviceEntryFaceAuthInteractor @Inject constructor() : DeviceEntryFaceAuthInteractor {
-    override val authenticationStatus: Flow<FaceAuthenticationStatus>
-        get() = emptyFlow()
-    override val detectionStatus: Flow<FaceDetectionStatus>
-        get() = emptyFlow()
+    override val authenticationStatus: Flow<FaceAuthenticationStatus> = emptyFlow()
+    override val detectionStatus: Flow<FaceDetectionStatus> = emptyFlow()
+    override val lockedOut: Flow<Boolean> = emptyFlow()
+    override val authenticated: Flow<Boolean> = emptyFlow()
 
     override fun canFaceAuthRun(): Boolean = false
 
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
index 0c9fbc2..a7266503 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
@@ -272,6 +272,8 @@
 
     /** Provide the status of face detection */
     override val detectionStatus = repository.detectionStatus
+    override val lockedOut: Flow<Boolean> = repository.isLockedOut
+    override val authenticated: Flow<Boolean> = repository.isAuthenticated
 
     private fun runFaceAuth(uiEvent: FaceAuthUiEvent, fallbackToDetect: Boolean) {
         if (repository.isLockedOut.value) {
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceEntryRestrictionReason.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceEntryRestrictionReason.kt
new file mode 100644
index 0000000..5b672ac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceEntryRestrictionReason.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.shared.model
+
+/** List of reasons why device entry can be restricted to certain authentication methods. */
+enum class DeviceEntryRestrictionReason {
+    /**
+     * Reason: Lockdown initiated by the user.
+     *
+     * Restriction: Only bouncer based device entry is allowed.
+     */
+    UserLockdown,
+
+    /**
+     * Reason: Not unlocked since reboot.
+     *
+     * Restriction: Only bouncer based device entry is allowed.
+     */
+    DeviceNotUnlockedSinceReboot,
+
+    /**
+     * Reason: Not unlocked since reboot after a mainline update.
+     *
+     * Restriction: Only bouncer based device entry is allowed.
+     */
+    DeviceNotUnlockedSinceMainlineUpdate,
+
+    /**
+     * Reason: Lockdown initiated by admin through installed device policy
+     *
+     * Restriction: Only bouncer based device entry is allowed.
+     */
+    PolicyLockdown,
+
+    /**
+     * Reason: Device entry credentials need to be used for an unattended update at a later point in
+     * time.
+     *
+     * Restriction: Only bouncer based device entry is allowed.
+     */
+    UnattendedUpdate,
+
+    /**
+     * Reason: Device was not unlocked using PIN/Pattern/Password for a prolonged period of time.
+     *
+     * Restriction: Only bouncer based device entry is allowed.
+     */
+    SecurityTimeout,
+
+    /**
+     * Reason: A "class 3"/strong biometrics device entry method was locked out after many incorrect
+     * authentication attempts.
+     *
+     * Restriction: Only bouncer based device entry is allowed.
+     *
+     * @see
+     *   [Biometric classes](https://source.android.com/docs/security/features/biometric/measure#biometric-classes)
+     */
+    StrongBiometricsLockedOut,
+
+    /**
+     * Reason: A weak (class 2)/convenience (class 3) strength face biometrics device entry method
+     * was locked out after many incorrect authentication attempts.
+     *
+     * Restriction: Only stronger authentication methods (class 3 or bouncer) are allowed.
+     *
+     * @see
+     *   [Biometric classes](https://source.android.com/docs/security/features/biometric/measure#biometric-classes)
+     */
+    NonStrongFaceLockedOut,
+
+    /**
+     * Reason: Device was last unlocked using a weak/convenience strength biometrics device entry
+     * method and a stronger authentication method wasn't used to unlock the device for a prolonged
+     * period of time.
+     *
+     * Restriction: Only stronger authentication methods (class 3 or bouncer) are allowed.
+     *
+     * @see
+     *   [Biometric classes](https://source.android.com/docs/security/features/biometric/measure#biometric-classes)
+     */
+    NonStrongBiometricsSecurityTimeout,
+
+    /**
+     * Reason: A trust agent that was granting trust has either expired or disabled by the user by
+     * opening the power menu.
+     *
+     * Restriction: Only non trust agent device entry methods are allowed.
+     */
+    TrustAgentDisabled,
+
+    /**
+     * Reason: Theft protection is enabled after too many unlock attempts.
+     *
+     * Restriction: Only stronger authentication methods (class 3 or bouncer) are allowed.
+     */
+    AdaptiveAuthRequest,
+
+    /**
+     * Reason: Bouncer was locked out after too many incorrect authentication attempts.
+     *
+     * Restriction: Only bouncer based device entry is allowed.
+     */
+    BouncerLockedOut,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartable.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartable.kt
index 6cd94c6..1452526 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartable.kt
@@ -30,7 +30,7 @@
 class HomeControlsDreamStartable
 @Inject
 constructor(
-    private val context: Context,
+    context: Context,
     private val packageManager: PackageManager,
     private val homeControlsComponentInteractor: HomeControlsComponentInteractor,
     @Background private val bgScope: CoroutineScope,
@@ -39,10 +39,13 @@
     private val componentName = ComponentName(context, HomeControlsDreamService::class.java)
 
     override fun start() {
-        if (!homePanelDream()) return
         bgScope.launch {
-            homeControlsComponentInteractor.panelComponent.collect { selectedPanelComponent ->
-                setEnableHomeControlPanel(selectedPanelComponent != null)
+            if (homePanelDream()) {
+                homeControlsComponentInteractor.panelComponent.collect { selectedPanelComponent ->
+                    setEnableHomeControlPanel(selectedPanelComponent != null)
+                }
+            } else {
+                setEnableHomeControlPanel(false)
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 6bb84649..a199fea 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -76,23 +76,6 @@
     val NOTIFICATION_MEMORY_LOGGING_ENABLED =
             releasedFlag("notification_memory_logging_enabled")
 
-    // TODO(b/260335638): Tracking Bug
-    @JvmField
-    val NOTIFICATION_INLINE_REPLY_ANIMATION = releasedFlag("notification_inline_reply_animation")
-
-    // TODO(b/288326013): Tracking Bug
-    @JvmField
-    val NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION =
-        unreleasedFlag("notification_async_hybrid_view_inflation", teamfood = false)
-
-    @JvmField
-    val ANIMATED_NOTIFICATION_SHADE_INSETS =
-        releasedFlag("animated_notification_shade_insets")
-
-    // TODO(b/268005230): Tracking Bug
-    @JvmField
-    val SENSITIVE_REVEAL_ANIM = releasedFlag("sensitive_reveal_anim")
-
     // TODO(b/280783617): Tracking Bug
     @Keep
     @JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
index 6fa20de..1e3c604 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
@@ -20,36 +20,34 @@
 import javax.inject.Inject
 import javax.inject.Singleton
 
-/**
- * Proxy to make {@link SystemProperties} easily testable.
- */
+/** Proxy to make {@link SystemProperties} easily testable. */
 @Singleton
 open class SystemPropertiesHelper @Inject constructor() {
-    fun get(name: String): String {
+    open fun get(name: String): String {
         return SystemProperties.get(name)
     }
 
-    fun get(name: String, def: String?): String {
+    open fun get(name: String, def: String?): String {
         return SystemProperties.get(name, def)
     }
 
-    fun getBoolean(name: String, default: Boolean): Boolean {
+    open fun getBoolean(name: String, default: Boolean): Boolean {
         return SystemProperties.getBoolean(name, default)
     }
 
-    fun setBoolean(name: String, value: Boolean) {
+    open fun setBoolean(name: String, value: Boolean) {
         SystemProperties.set(name, if (value) "1" else "0")
     }
 
-    fun set(name: String, value: String) {
+    open fun set(name: String, value: String) {
         SystemProperties.set(name, value)
     }
 
-    fun set(name: String, value: Int) {
+    open fun set(name: String, value: Int) {
         set(name, value.toString())
     }
 
-    fun erase(name: String) {
+    open fun erase(name: String) {
         set(name, "")
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/LongPressHapticBuilder.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/LongPressHapticBuilder.kt
new file mode 100644
index 0000000..0143b85
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/LongPressHapticBuilder.kt
@@ -0,0 +1,115 @@
+/*
+ * 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.haptics.qs
+
+import android.os.VibrationEffect
+import android.util.Log
+import kotlin.math.max
+
+object LongPressHapticBuilder {
+
+    const val INVALID_DURATION = 0 /* in ms */
+
+    private const val TAG = "LongPressHapticBuilder"
+    private const val SPIN_SCALE = 0.2f
+    private const val CLICK_SCALE = 0.5f
+    private const val LOW_TICK_SCALE = 0.08f
+    private const val WARMUP_TIME = 75 /* in ms */
+    private const val DAMPING_TIME = 24 /* in ms */
+
+    /** Create the signal that indicates that a long-press action is available. */
+    fun createLongPressHint(
+        lowTickDuration: Int,
+        spinDuration: Int,
+        effectDuration: Int
+    ): VibrationEffect? {
+        if (lowTickDuration == 0 || spinDuration == 0) {
+            Log.d(
+                TAG,
+                "The LOW_TICK and/or SPIN primitives are not supported. No signal created.",
+            )
+            return null
+        }
+        if (effectDuration < WARMUP_TIME + spinDuration + DAMPING_TIME) {
+            Log.d(
+                TAG,
+                "Cannot fit long-press hint signal in the effect duration. No signal created",
+            )
+            return null
+        }
+
+        val nLowTicks = WARMUP_TIME / lowTickDuration
+        val rampDownLowTicks = DAMPING_TIME / lowTickDuration
+        val composition = VibrationEffect.startComposition()
+
+        // Warmup low ticks
+        repeat(nLowTicks) {
+            composition.addPrimitive(
+                VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+                LOW_TICK_SCALE,
+                0,
+            )
+        }
+
+        // Spin effect
+        composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN, SPIN_SCALE, 0)
+
+        // Damping low ticks
+        repeat(rampDownLowTicks) { i ->
+            composition.addPrimitive(
+                VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+                LOW_TICK_SCALE / (i + 1),
+                0,
+            )
+        }
+
+        return composition.compose()
+    }
+
+    /** Create a "snapping" effect that triggers at the end of a long-press gesture */
+    fun createSnapEffect(): VibrationEffect? =
+        VibrationEffect.startComposition()
+            .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, CLICK_SCALE, 0)
+            .compose()
+
+    /** Creates a signal that indicates the reversal of the long-press animation. */
+    fun createReversedEffect(
+        pausedProgress: Float,
+        lowTickDuration: Int,
+        effectDuration: Int,
+    ): VibrationEffect? {
+        val duration = pausedProgress * effectDuration
+        if (duration == 0f) return null
+
+        if (lowTickDuration == 0) {
+            Log.d(TAG, "Cannot play reverse haptics because LOW_TICK is not supported")
+            return null
+        }
+
+        val nLowTicks = (duration / lowTickDuration).toInt()
+        if (nLowTicks == 0) return null
+
+        val composition = VibrationEffect.startComposition()
+        var scale: Float
+        val step = LOW_TICK_SCALE / nLowTicks
+        repeat(nLowTicks) { i ->
+            scale = max(LOW_TICK_SCALE - step * i, 0f)
+            composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, scale, 0)
+        }
+        return composition.compose()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
new file mode 100644
index 0000000..ec72a14
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -0,0 +1,237 @@
+/*
+ * 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.haptics.qs
+
+import android.animation.ValueAnimator
+import android.annotation.SuppressLint
+import android.os.VibrationEffect
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewConfiguration
+import android.view.animation.AccelerateDecelerateInterpolator
+import androidx.annotation.VisibleForTesting
+import androidx.core.animation.doOnCancel
+import androidx.core.animation.doOnEnd
+import androidx.core.animation.doOnStart
+import com.android.systemui.statusbar.VibratorHelper
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+
+/**
+ * A class that handles the long press visuo-haptic effect for a QS tile.
+ *
+ * The class is also a [View.OnTouchListener] to handle the touch events, clicks and long-press
+ * gestures of the tile. The class also provides a [State] that can be used to determine the current
+ * state of the long press effect.
+ *
+ * @property[vibratorHelper] The [VibratorHelper] to deliver haptic effects.
+ * @property[effectDuration] The duration of the effect in ms.
+ */
+class QSLongPressEffect(
+    private val vibratorHelper: VibratorHelper?,
+    private val effectDuration: Int,
+) : View.OnTouchListener {
+
+    /** Current state */
+    var state = State.IDLE
+        @VisibleForTesting set
+
+    /** Flows for view control and action */
+    private val _effectProgress = MutableStateFlow<Float?>(null)
+    val effectProgress = _effectProgress.asStateFlow()
+
+    private val _actionType = MutableStateFlow<ActionType?>(null)
+    val actionType = _actionType.asStateFlow()
+
+    /** Haptic effects */
+    private val durations =
+        vibratorHelper?.getPrimitiveDurations(
+            VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+            VibrationEffect.Composition.PRIMITIVE_SPIN
+        )
+
+    private val longPressHint =
+        LongPressHapticBuilder.createLongPressHint(
+            durations?.get(0) ?: LongPressHapticBuilder.INVALID_DURATION,
+            durations?.get(1) ?: LongPressHapticBuilder.INVALID_DURATION,
+            effectDuration
+        )
+
+    private val snapEffect = LongPressHapticBuilder.createSnapEffect()
+
+    /* A coroutine scope and a timer job that waits for the pressedTimeout */
+    var scope: CoroutineScope? = null
+    private var waitJob: Job? = null
+
+    private val effectAnimator =
+        ValueAnimator.ofFloat(0f, 1f).apply {
+            duration = effectDuration.toLong()
+            interpolator = AccelerateDecelerateInterpolator()
+
+            doOnStart { handleAnimationStart() }
+            addUpdateListener { _effectProgress.value = animatedValue as Float }
+            doOnEnd { handleAnimationComplete() }
+            doOnCancel { handleAnimationCancel() }
+        }
+
+    private fun reverse() {
+        val pausedProgress = effectAnimator.animatedFraction
+        val effect =
+            LongPressHapticBuilder.createReversedEffect(
+                pausedProgress,
+                durations?.get(0) ?: 0,
+                effectDuration,
+            )
+        vibratorHelper?.cancel()
+        vibrate(effect)
+        effectAnimator.reverse()
+    }
+
+    private fun vibrate(effect: VibrationEffect?) {
+        if (vibratorHelper != null && effect != null) {
+            vibratorHelper.vibrate(effect)
+        }
+    }
+
+    /**
+     * Handle relevant touch events for the operation of a Tile.
+     *
+     * A click action is performed following the relevant logic that originates from the
+     * [MotionEvent.ACTION_UP] event depending on the current state.
+     */
+    @SuppressLint("ClickableViewAccessibility")
+    override fun onTouch(view: View?, event: MotionEvent?): Boolean {
+        when (event?.actionMasked) {
+            MotionEvent.ACTION_DOWN -> handleActionDown()
+            MotionEvent.ACTION_UP -> handleActionUp()
+            MotionEvent.ACTION_CANCEL -> handleActionCancel()
+        }
+        return true
+    }
+
+    private fun handleActionDown() {
+        when (state) {
+            State.IDLE -> {
+                startPressedTimeoutWait()
+                state = State.TIMEOUT_WAIT
+            }
+            State.RUNNING_BACKWARDS -> effectAnimator.cancel()
+            else -> {}
+        }
+    }
+
+    private fun startPressedTimeoutWait() {
+        waitJob =
+            scope?.launch {
+                try {
+                    delay(PRESSED_TIMEOUT)
+                    handleTimeoutComplete()
+                } catch (_: CancellationException) {
+                    state = State.IDLE
+                }
+            }
+    }
+
+    private fun handleActionUp() {
+        when (state) {
+            State.TIMEOUT_WAIT -> {
+                waitJob?.cancel()
+                _actionType.value = ActionType.CLICK
+                state = State.IDLE
+            }
+            State.RUNNING_FORWARD -> {
+                reverse()
+                state = State.RUNNING_BACKWARDS
+            }
+            else -> {}
+        }
+    }
+
+    private fun handleActionCancel() {
+        when (state) {
+            State.TIMEOUT_WAIT -> {
+                waitJob?.cancel()
+                state = State.IDLE
+            }
+            State.RUNNING_FORWARD -> {
+                reverse()
+                state = State.RUNNING_BACKWARDS
+            }
+            else -> {}
+        }
+    }
+
+    private fun handleAnimationStart() {
+        vibrate(longPressHint)
+        state = State.RUNNING_FORWARD
+    }
+
+    /** This function is called both when an animator completes or gets cancelled */
+    private fun handleAnimationComplete() {
+        if (state == State.RUNNING_FORWARD) {
+            vibrate(snapEffect)
+            _actionType.value = ActionType.LONG_PRESS
+            _effectProgress.value = null
+        }
+        if (state != State.TIMEOUT_WAIT) {
+            // This will happen if the animator did not finish by being cancelled
+            state = State.IDLE
+        }
+    }
+
+    private fun handleAnimationCancel() {
+        _effectProgress.value = 0f
+        startPressedTimeoutWait()
+        state = State.TIMEOUT_WAIT
+    }
+
+    private fun handleTimeoutComplete() {
+        if (state == State.TIMEOUT_WAIT && !effectAnimator.isRunning) {
+            effectAnimator.start()
+        }
+    }
+
+    fun clearActionType() {
+        _actionType.value = null
+    }
+
+    enum class State {
+        IDLE, /* The effect is idle waiting for touch input */
+        TIMEOUT_WAIT, /* The effect is waiting for a [PRESSED_TIMEOUT] period */
+        RUNNING_FORWARD, /* The effect is running normally */
+        RUNNING_BACKWARDS, /* The effect was interrupted and is now running backwards */
+    }
+
+    /* A type of action to perform on the view depending on the effect's state and logic */
+    enum class ActionType {
+        CLICK,
+        LONG_PRESS,
+    }
+
+    companion object {
+        /**
+         * A timeout to let the tile resolve if it is being swiped/scrolled. Since QS tiles are
+         * inside a scrollable container, they will be considered pressed only after a tap timeout.
+         */
+        val PRESSED_TIMEOUT = ViewConfiguration.getTapTimeout().toLong() + 20L
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
new file mode 100644
index 0000000..e298154
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics.qs
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.qs.tileimpl.QSTileViewImpl
+import kotlinx.coroutines.launch
+
+object QSLongPressEffectViewBinder {
+
+    fun bind(
+        tile: QSTileViewImpl,
+        effect: QSLongPressEffect?,
+    ) {
+        if (effect == null) return
+
+        tile.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                effect.scope = this
+
+                launch {
+                    effect.effectProgress.collect { progress ->
+                        progress?.let {
+                            if (it == 0f) {
+                                tile.bringToFront()
+                            }
+                            tile.updateLongPressEffectProperties(it)
+                        }
+                    }
+                }
+
+                launch {
+                    effect.actionType.collect { action ->
+                        action?.let {
+                            when (it) {
+                                QSLongPressEffect.ActionType.CLICK -> tile.performClick()
+                                QSLongPressEffect.ActionType.LONG_PRESS -> tile.performLongClick()
+                            }
+                            effect.clearActionType()
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
index f5f5571..882f231 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
@@ -175,7 +175,6 @@
         pw.println("isFingerprintAuthCurrentlyAllowed=${isFingerprintAuthCurrentlyAllowed.value}")
         pw.println("isNonStrongBiometricAllowed=${isNonStrongBiometricAllowed.value}")
         pw.println("isStrongBiometricAllowed=${isStrongBiometricAllowed.value}")
-        pw.println("isFingerprintEnabledByDevicePolicy=${isFingerprintEnabledByDevicePolicy.value}")
     }
 
     /** UserId of the current selected user. */
@@ -324,22 +323,14 @@
             else isNonStrongBiometricAllowed
         }
 
-    private val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean> =
-        selectedUserId
-            .flatMapLatest { userId ->
-                devicePolicyChangedForAllUsers
-                    .transformLatest { emit(devicePolicyManager.isFingerprintDisabled(userId)) }
-                    .flowOn(backgroundDispatcher)
-                    .distinctUntilChanged()
-            }
-            .stateIn(
-                scope,
-                started = SharingStarted.Eagerly,
-                initialValue =
-                    devicePolicyManager.isFingerprintDisabled(
-                        userRepository.getSelectedUserInfo().id
-                    )
-            )
+    private val isFingerprintEnabledByDevicePolicy: Flow<Boolean> =
+        selectedUserId.flatMapLatest { userId ->
+            devicePolicyChangedForAllUsers
+                .transformLatest { emit(devicePolicyManager.isFingerprintDisabled(userId)) }
+                .onStart { emit(devicePolicyManager.isFingerprintDisabled(userId)) }
+                .flowOn(backgroundDispatcher)
+                .distinctUntilChanged()
+        }
 
     override val isFingerprintEnrolledAndEnabled: StateFlow<Boolean> =
         isFingerprintEnrolled
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TrustInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TrustInteractor.kt
new file mode 100644
index 0000000..2ff6e16
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TrustInteractor.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.TrustRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+
+/** Encapsulates any state relevant to trust agents and trust grants. */
+@SysUISingleton
+class TrustInteractor @Inject constructor(repository: TrustRepository) {
+    /**
+     * Whether the current user has a trust agent enabled. This is true if the user has at least one
+     * trust agent enabled in settings.
+     */
+    val isEnrolledAndEnabled: StateFlow<Boolean> = repository.isCurrentUserTrustUsuallyManaged
+
+    /**
+     * Whether the current user's trust agent is currently allowed, this will be false if trust
+     * agent is disabled for any reason (security timeout, disabled on lock screen by opening the
+     * power menu, etc), it does not include temporary biometric lockouts.
+     */
+    val isTrustAgentCurrentlyAllowed: StateFlow<Boolean> = repository.isCurrentUserTrustManaged
+
+    /** Whether the current user is trusted by any of the enabled trust agents. */
+    val isTrusted: Flow<Boolean> = repository.isCurrentUserTrusted
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt
index 08904b6..d6f3634 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt
@@ -32,6 +32,9 @@
     val isPrimaryAuthRequiredAfterTimeout =
         containsFlag(flag, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT)
 
+    val isPrimaryAuthRequiredAfterLockout =
+        containsFlag(flag, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT)
+
     val isPrimaryAuthRequiredAfterDpmLockdown =
         containsFlag(
             flag,
@@ -47,7 +50,7 @@
             LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED
         )
 
-    val primaryAuthRequiredForUnattendedUpdate =
+    val isPrimaryAuthRequiredForUnattendedUpdate =
         containsFlag(
             flag,
             LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE
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 e5b5964..abf2372 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
@@ -79,6 +79,8 @@
 
     val notificationAlpha: Flow<Float> = keyguardAlpha
 
+    val shortcutsAlpha: Flow<Float> = keyguardAlpha
+
     val notificationTranslationX: Flow<Float> =
         keyguardTranslationX.map { it.value }.filterNotNull()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
index 4db942cc..c4383fc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
@@ -52,6 +52,7 @@
     occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
     offToLockscreenTransitionViewModel: OffToLockscreenTransitionViewModel,
     primaryBouncerToLockscreenTransitionViewModel: PrimaryBouncerToLockscreenTransitionViewModel,
+    glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel,
     lockscreenToAodTransitionViewModel: LockscreenToAodTransitionViewModel,
     lockscreenToDozingTransitionViewModel: LockscreenToDozingTransitionViewModel,
     lockscreenToDreamingHostedTransitionViewModel: LockscreenToDreamingHostedTransitionViewModel,
@@ -59,6 +60,7 @@
     lockscreenToGoneTransitionViewModel: LockscreenToGoneTransitionViewModel,
     lockscreenToOccludedTransitionViewModel: LockscreenToOccludedTransitionViewModel,
     lockscreenToPrimaryBouncerTransitionViewModel: LockscreenToPrimaryBouncerTransitionViewModel,
+    lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel,
     transitionInteractor: KeyguardTransitionInteractor,
 ) {
 
@@ -110,6 +112,7 @@
             occludedToLockscreenTransitionViewModel.shortcutsAlpha,
             offToLockscreenTransitionViewModel.shortcutsAlpha,
             primaryBouncerToLockscreenTransitionViewModel.shortcutsAlpha,
+            glanceableHubToLockscreenTransitionViewModel.shortcutsAlpha,
         )
 
     /** alpha while fading the quick affordances in */
@@ -122,6 +125,7 @@
             lockscreenToGoneTransitionViewModel.shortcutsAlpha,
             lockscreenToOccludedTransitionViewModel.shortcutsAlpha,
             lockscreenToPrimaryBouncerTransitionViewModel.shortcutsAlpha,
+            lockscreenToGlanceableHubTransitionViewModel.shortcutsAlpha,
             shadeExpansionAlpha,
         )
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index 288ef3c..993e81b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -14,9 +14,15 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -27,9 +33,10 @@
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.stateIn
 
 /** Models UI state and handles user input for the lockscreen scene. */
@@ -44,37 +51,69 @@
     val longPress: KeyguardLongPressViewModel,
     val notifications: NotificationsPlaceholderViewModel,
 ) {
-    /** The key of the scene we should switch to when swiping up. */
-    val upDestinationSceneKey: StateFlow<SceneKey> =
-        deviceEntryInteractor.isUnlocked
-            .map { isUnlocked -> upDestinationSceneKey(isUnlocked) }
+    val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+        combine(
+                deviceEntryInteractor.isUnlocked,
+                communalInteractor.isCommunalAvailable,
+                shadeInteractor.shadeMode,
+            ) { isDeviceUnlocked, isCommunalAvailable, shadeMode ->
+                destinationScenes(
+                    isDeviceUnlocked = isDeviceUnlocked,
+                    isCommunalAvailable = isCommunalAvailable,
+                    shadeMode = shadeMode,
+                )
+            }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue = upDestinationSceneKey(deviceEntryInteractor.isUnlocked.value),
+                initialValue =
+                    destinationScenes(
+                        isDeviceUnlocked = deviceEntryInteractor.isUnlocked.value,
+                        isCommunalAvailable = false,
+                        shadeMode = shadeInteractor.shadeMode.value,
+                    ),
             )
 
-    private fun upDestinationSceneKey(isUnlocked: Boolean): SceneKey {
-        return if (isUnlocked) Scenes.Gone else Scenes.Bouncer
+    private fun destinationScenes(
+        isDeviceUnlocked: Boolean,
+        isCommunalAvailable: Boolean,
+        shadeMode: ShadeMode,
+    ): Map<UserAction, UserActionResult> {
+        val quickSettingsIfSingleShade =
+            if (shadeMode is ShadeMode.Single) {
+                Scenes.QuickSettings
+            } else {
+                Scenes.Shade
+            }
+
+        return mapOf(
+                Swipe.Left to UserActionResult(Scenes.Communal).takeIf { isCommunalAvailable },
+                Swipe.Up to if (isDeviceUnlocked) Scenes.Gone else Scenes.Bouncer,
+
+                // Swiping down from the top edge goes to QS (or shade if in split shade mode).
+                swipeDownFromTop(pointerCount = 1) to quickSettingsIfSingleShade,
+                swipeDownFromTop(pointerCount = 2) to quickSettingsIfSingleShade,
+
+                // Swiping down, not from the edge, always navigates to the shade scene.
+                swipeDown(pointerCount = 1) to Scenes.Shade,
+                swipeDown(pointerCount = 2) to Scenes.Shade,
+            )
+            .filterValues { it != null }
+            .mapValues { checkNotNull(it.value) }
     }
 
-    /** The key of the scene we should switch to when swiping left. */
-    val leftDestinationSceneKey: StateFlow<SceneKey?> =
-        communalInteractor.isCommunalAvailable
-            .map { available -> if (available) Scenes.Communal else null }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = null,
-            )
+    private fun swipeDownFromTop(pointerCount: Int): Swipe {
+        return Swipe(
+            SwipeDirection.Down,
+            fromSource = Edge.Top,
+            pointerCount = pointerCount,
+        )
+    }
 
-    /** The key of the scene we should switch to when swiping down from the top edge. */
-    val downFromTopEdgeDestinationSceneKey: StateFlow<SceneKey?> =
-        shadeInteractor.shadeMode
-            .map { shadeMode -> Scenes.QuickSettings.takeIf { shadeMode is ShadeMode.Single } }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = null,
-            )
+    private fun swipeDown(pointerCount: Int): Swipe {
+        return Swipe(
+            SwipeDirection.Down,
+            pointerCount = pointerCount,
+        )
+    }
 }
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 978e71e..b7f7b06 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
@@ -81,6 +81,8 @@
 
     val notificationAlpha: Flow<Float> = keyguardAlpha
 
+    val shortcutsAlpha: Flow<Float> = keyguardAlpha
+
     val notificationTranslationX: Flow<Float> =
         keyguardTranslationX.map { it.value }.filterNotNull()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
index 027a739..bf22563 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
 import javax.inject.Inject
 
-class MediaCarouselViewModel @Inject constructor(mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean = mediaDataManager.hasActiveMediaOrRecommendation()
+class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
+    val isMediaVisible: Boolean
+        get() = mediaDataManager.hasActiveMediaOrRecommendation()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index f4903f1..768bb8e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -131,6 +131,7 @@
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
 import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
 import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.shared.rotation.RotationButton;
@@ -199,6 +200,7 @@
     private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
     private final KeyguardStateController mKeyguardStateController;
     private final ShadeViewController mShadeViewController;
+    private final PanelExpansionInteractor mPanelExpansionInteractor;
     private final NotificationRemoteInputManager mNotificationRemoteInputManager;
     private final OverviewProxyService mOverviewProxyService;
     private final NavigationModeController mNavigationModeController;
@@ -537,6 +539,7 @@
             Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
             KeyguardStateController keyguardStateController,
             ShadeViewController shadeViewController,
+            PanelExpansionInteractor panelExpansionInteractor,
             NotificationRemoteInputManager notificationRemoteInputManager,
             NotificationShadeDepthController notificationShadeDepthController,
             @Main Handler mainHandler,
@@ -575,6 +578,7 @@
         mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
         mKeyguardStateController = keyguardStateController;
         mShadeViewController = shadeViewController;
+        mPanelExpansionInteractor = panelExpansionInteractor;
         mNotificationRemoteInputManager = notificationRemoteInputManager;
         mOverviewProxyService = overviewProxyService;
         mNavigationModeController = navigationModeController;
@@ -749,7 +753,7 @@
         final Display display = mView.getDisplay();
         mView.setComponents(mRecentsOptional);
         if (mCentralSurfacesOptionalLazy.get().isPresent()) {
-            mView.setComponents(mShadeViewController);
+            mView.setComponents(mShadeViewController, mPanelExpansionInteractor);
         }
         mView.setDisabledFlags(mDisabledFlags1, mSysUiFlagsContainer);
         mView.setOnVerticalChangedListener(this::onVerticalChanged);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index c5190a2..1927f49 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -75,6 +75,7 @@
 import com.android.systemui.res.R;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
 import com.android.systemui.shared.rotation.FloatingRotationButton;
 import com.android.systemui.shared.rotation.RotationButton.RotationButtonUpdatesCallback;
 import com.android.systemui.shared.rotation.RotationButtonController;
@@ -149,7 +150,9 @@
     private NavigationBarInflaterView mNavigationInflaterView;
     private Optional<Recents> mRecentsOptional = Optional.empty();
     @Nullable
-    private ShadeViewController mPanelView;
+    private ShadeViewController mShadeViewController;
+    @Nullable
+    private PanelExpansionInteractor mPanelExpansionInteractor;
     private RotationContextButton mRotationContextButton;
     private FloatingRotationButton mFloatingRotationButton;
     private RotationButtonController mRotationButtonController;
@@ -347,8 +350,9 @@
     }
 
     /** */
-    public void setComponents(ShadeViewController panel) {
-        mPanelView = panel;
+    public void setComponents(ShadeViewController svc, PanelExpansionInteractor pei) {
+        mShadeViewController = svc;
+        mPanelExpansionInteractor = pei;
         updatePanelSystemUiStateFlags();
     }
 
@@ -750,10 +754,10 @@
 
     private void updatePanelSystemUiStateFlags() {
         if (SysUiState.DEBUG) {
-            Log.d(TAG, "Updating panel sysui state flags: panelView=" + mPanelView);
+            Log.d(TAG, "Updating panel sysui state flags: panelView=" + mShadeViewController);
         }
-        if (mPanelView != null) {
-            mPanelView.updateSystemUiStateFlags();
+        if (mShadeViewController != null) {
+            mShadeViewController.updateSystemUiStateFlags();
         }
     }
 
@@ -801,7 +805,8 @@
      */
     void updateSlippery() {
         setSlippery(!isQuickStepSwipeUpEnabled() ||
-                (mPanelView != null && mPanelView.isFullyExpanded() && !mPanelView.isCollapsing()));
+                (mPanelExpansionInteractor != null && mPanelExpansionInteractor.isFullyExpanded()
+                        && !mPanelExpansionInteractor.isCollapsing()));
     }
 
     void setSlippery(boolean slippery) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 91c86df..9d0ea5e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -19,6 +19,7 @@
 import static android.view.InputDevice.SOURCE_TOUCHPAD;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
 
+import static com.android.systemui.Flags.edgebackGestureHandlerGetRunningTasksBackground;
 import static com.android.systemui.classifier.Classifier.BACK_GESTURE;
 import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadScroll;
 import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadThreeFingerSwipe;
@@ -54,7 +55,6 @@
 import android.view.IWindowManager;
 import android.view.InputDevice;
 import android.view.InputEvent;
-import android.view.InputMonitor;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -104,6 +104,7 @@
 import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
@@ -151,7 +152,12 @@
     private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
         @Override
         public void onTaskStackChanged() {
-            mGestureBlockingActivityRunning = isGestureBlockingActivityRunning();
+            if (edgebackGestureHandlerGetRunningTasksBackground()) {
+                mBackgroundExecutor.execute(() -> mGestureBlockingActivityRunning.set(
+                        isGestureBlockingActivityRunning()));
+            } else {
+                mGestureBlockingActivityRunning.set(isGestureBlockingActivityRunning());
+            }
         }
         @Override
         public void onTaskCreated(int taskId, ComponentName componentName) {
@@ -241,6 +247,8 @@
 
     private final PointF mDownPoint = new PointF();
     private final PointF mEndPoint = new PointF();
+    private AtomicBoolean mGestureBlockingActivityRunning = new AtomicBoolean();
+
     private boolean mThresholdCrossed = false;
     private boolean mAllowGesture = false;
     private boolean mLogGesture = false;
@@ -256,7 +264,6 @@
     private boolean mIsEnabled;
     private boolean mIsNavBarShownTransiently;
     private boolean mIsBackGestureAllowed;
-    private boolean mGestureBlockingActivityRunning;
     private boolean mIsNewBackAffordanceEnabled;
     private boolean mIsTrackpadGestureFeaturesEnabled;
     private boolean mIsTrackpadThreeFingerSwipe;
@@ -1017,7 +1024,7 @@
             mInRejectedExclusion = false;
             boolean isWithinInsets = isWithinInsets((int) ev.getX(), (int) ev.getY());
             boolean isBackAllowedCommon = !mDisabledForQuickstep && mIsBackGestureAllowed
-                    && !mGestureBlockingActivityRunning
+                    && !mGestureBlockingActivityRunning.get()
                     && !QuickStepContract.isBackGestureDisabled(mSysUiFlags,
                             mIsTrackpadThreeFingerSwipe)
                     && !isTrackpadScroll(mIsTrackpadGestureFeaturesEnabled, ev);
@@ -1053,8 +1060,8 @@
                     curTime, curTimeStr, mAllowGesture, mIsTrackpadThreeFingerSwipe,
                     mIsOnLeftEdge, mDeferSetIsOnLeftEdge, mIsBackGestureAllowed,
                     QuickStepContract.isBackGestureDisabled(mSysUiFlags,
-                            mIsTrackpadThreeFingerSwipe),
-                    mDisabledForQuickstep, mGestureBlockingActivityRunning, mIsInPip, mDisplaySize,
+                            mIsTrackpadThreeFingerSwipe), mDisabledForQuickstep,
+                    mGestureBlockingActivityRunning.get(), mIsInPip, mDisplaySize,
                     mEdgeWidthLeft, mLeftInset, mEdgeWidthRight, mRightInset, mExcludeRegion));
         } else if (mAllowGesture || mLogGesture) {
             if (!mThresholdCrossed) {
@@ -1236,7 +1243,7 @@
         pw.println("  mIsBackGestureAllowed=" + mIsBackGestureAllowed);
         pw.println("  mIsGestureHandlingEnabled=" + mIsGestureHandlingEnabled);
         pw.println("  mIsNavBarShownTransiently=" + mIsNavBarShownTransiently);
-        pw.println("  mGestureBlockingActivityRunning=" + mGestureBlockingActivityRunning);
+        pw.println("  mGestureBlockingActivityRunning=" + mGestureBlockingActivityRunning.get());
         pw.println("  mAllowGesture=" + mAllowGesture);
         pw.println("  mUseMLModel=" + mUseMLModel);
         pw.println("  mDisabledForQuickstep=" + mDisabledForQuickstep);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 2440651..cd65119 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -38,6 +38,7 @@
 import com.android.systemui.settings.brightness.BrightnessController;
 import com.android.systemui.settings.brightness.BrightnessMirrorHandler;
 import com.android.systemui.settings.brightness.BrightnessSliderController;
+import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.statusbar.policy.SplitShadeStateController;
@@ -90,9 +91,11 @@
             FalsingManager falsingManager,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             SplitShadeStateController splitShadeStateController,
-            SceneContainerFlags sceneContainerFlags) {
+            SceneContainerFlags sceneContainerFlags,
+            VibratorHelper vibratorHelper) {
         super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost,
-                metricsLogger, uiEventLogger, qsLogger, dumpManager, splitShadeStateController);
+                metricsLogger, uiEventLogger, qsLogger, dumpManager, splitShadeStateController,
+                vibratorHelper);
         mTunerService = tunerService;
         mQsCustomizerController = qsCustomizerController;
         mQsTileRevealControllerFactory = qsTileRevealControllerFactory;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 975c871..5e12b9d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -39,6 +39,7 @@
 import com.android.systemui.qs.external.CustomTile;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileViewImpl;
+import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.policy.SplitShadeStateController;
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.animation.DisappearParameters;
@@ -87,6 +88,8 @@
 
     private SplitShadeStateController mSplitShadeStateController;
 
+    private final VibratorHelper mVibratorHelper;
+
     @VisibleForTesting
     protected final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
             new QSPanel.OnConfigurationChangedListener() {
@@ -144,7 +147,8 @@
             UiEventLogger uiEventLogger,
             QSLogger qsLogger,
             DumpManager dumpManager,
-            SplitShadeStateController splitShadeStateController
+            SplitShadeStateController splitShadeStateController,
+            VibratorHelper vibratorHelper
     ) {
         super(view);
         mHost = host;
@@ -158,6 +162,7 @@
         mSplitShadeStateController = splitShadeStateController;
         mShouldUseSplitNotificationShade =
                 mSplitShadeStateController.shouldUseSplitNotificationShade(getResources());
+        mVibratorHelper = vibratorHelper;
     }
 
     @Override
@@ -300,7 +305,8 @@
     }
 
     private void addTile(final QSTile tile, boolean collapsedView) {
-        final QSTileViewImpl tileView = new QSTileViewImpl(getContext(), collapsedView);
+        final QSTileViewImpl tileView = new QSTileViewImpl(
+                getContext(), collapsedView, mVibratorHelper);
         final TileRecord r = new TileRecord(tile, tileView);
         // TODO(b/250618218): Remove the QSLogger in QSTileViewImpl once we know the root cause of
         // b/250618218.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index a8e88da..05bb088 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -32,6 +32,7 @@
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.res.R;
+import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.policy.SplitShadeStateController;
 import com.android.systemui.util.leak.RotationUtils;
 
@@ -56,10 +57,11 @@
             @Named(QS_USING_COLLAPSED_LANDSCAPE_MEDIA)
                     Provider<Boolean> usingCollapsedLandscapeMediaProvider,
             MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
-            DumpManager dumpManager, SplitShadeStateController splitShadeStateController
+            DumpManager dumpManager, SplitShadeStateController splitShadeStateController,
+            VibratorHelper vibratorHelper
     ) {
         super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
-                uiEventLogger, qsLogger, dumpManager, splitShadeStateController);
+                uiEventLogger, qsLogger, dumpManager, splitShadeStateController, vibratorHelper);
         mUsingCollapsedLandscapeMediaProvider = usingCollapsedLandscapeMediaProvider;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSLongPressProperties.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSLongPressProperties.kt
new file mode 100644
index 0000000..a2ded6a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSLongPressProperties.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.qs.tileimpl
+
+/**
+ * List of properties that define the state of a tile during a long-press gesture.
+ *
+ * These properties are used during animation if a tile supports a long-press action.
+ */
+data class QSLongPressProperties(
+    var xScale: Float,
+    var yScale: Float,
+    var cornerRadius: Float,
+    var backgroundColor: Int,
+    var labelColor: Int,
+    var secondaryLabelColor: Int,
+    var chevronColor: Int,
+    var overlayColor: Int,
+    var iconColor: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 35cac4b..1456747 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -587,7 +587,7 @@
                     name = "handleClick";
                     if (mState.disabledByPolicy) {
                         Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
-                                mContext, mEnforcedAdmin);
+                                mEnforcedAdmin);
                         mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
                     } else {
                         mQSLogger.logHandleClick(mTileSpec, msg.arg1);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 6cc682a..63963de 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -26,6 +26,7 @@
 import android.graphics.Color
 import android.graphics.PorterDuff
 import android.graphics.drawable.Drawable
+import android.graphics.drawable.GradientDrawable
 import android.graphics.drawable.LayerDrawable
 import android.graphics.drawable.RippleDrawable
 import android.os.Trace
@@ -36,6 +37,7 @@
 import android.view.Gravity
 import android.view.LayoutInflater
 import android.view.View
+import android.view.ViewConfiguration
 import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import android.view.accessibility.AccessibilityNodeInfo
@@ -48,9 +50,12 @@
 import com.android.app.tracing.traceSection
 import com.android.settingslib.Utils
 import com.android.systemui.Flags
+import com.android.systemui.Flags.quickSettingsVisualHapticsLongpress
 import com.android.systemui.FontSizeUtils
 import com.android.systemui.animation.LaunchableView
 import com.android.systemui.animation.LaunchableViewDelegate
+import com.android.systemui.haptics.qs.QSLongPressEffect
+import com.android.systemui.haptics.qs.QSLongPressEffectViewBinder
 import com.android.systemui.plugins.qs.QSIconView
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.qs.QSTile.AdapterState
@@ -58,12 +63,15 @@
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH
 import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.children
 import java.util.Objects
 
 private const val TAG = "QSTileViewImpl"
 open class QSTileViewImpl @JvmOverloads constructor(
     context: Context,
-    private val collapsed: Boolean = false
+    private val collapsed: Boolean = false,
+    private val vibratorHelper: VibratorHelper? = null,
 ) : QSTileView(context), HeightOverrideable, LaunchableView {
 
     companion object {
@@ -163,6 +171,7 @@
     private var lastStateDescription: CharSequence? = null
     private var tileState = false
     private var lastState = INVALID
+    private var lastIconTint = 0
     private val launchableViewDelegate = LaunchableViewDelegate(
         this,
         superSetVisibility = { super.setVisibility(it) },
@@ -171,6 +180,12 @@
 
     private val locInScreen = IntArray(2)
 
+    /** Visuo-haptic long-press effects */
+    private var longPressEffect: QSLongPressEffect? = null
+    private var initialLongPressProperties: QSLongPressProperties? = null
+    private var finalLongPressProperties: QSLongPressProperties? = null
+    private val colorEvaluator = ArgbEvaluator.getInstance()
+
     init {
         val typedValue = TypedValue()
         if (!getContext().theme.resolveAttribute(R.attr.isQsTheme, typedValue, true)) {
@@ -339,6 +354,9 @@
                     true
                 }
         )
+        if (quickSettingsVisualHapticsLongpress()) {
+            isHapticFeedbackEnabled = false // Haptics will be handled by the [QSLongPressEffect]
+        }
     }
 
     private fun init(
@@ -589,6 +607,27 @@
 
         lastState = state.state
         lastDisabledByPolicy = state.disabledByPolicy
+        lastIconTint = icon.getColor(state)
+
+        // Long-press effects
+        if (quickSettingsVisualHapticsLongpress()){
+            if (state.handlesLongClick) {
+                // initialize the long-press effect and set it as the touch listener
+                showRippleEffect = false
+                initializeLongPressEffect()
+                setOnTouchListener(longPressEffect)
+                QSLongPressEffectViewBinder.bind(this, longPressEffect)
+            } else {
+                // Long-press effects might have been enabled before but the new state does not
+                // handle a long-press. In this case, we go back to the behaviour of a regular tile
+                // and clean-up the resources
+                showRippleEffect = isClickable
+                setOnTouchListener(null)
+                longPressEffect = null
+                initialLongPressProperties = null
+                finalLongPressProperties = null
+            }
+        }
     }
 
     private fun setAllColors(
@@ -709,6 +748,140 @@
         }
     }
 
+    override fun onActivityLaunchAnimationEnd() = resetLongPressEffectProperties()
+
+    fun updateLongPressEffectProperties(effectProgress: Float) {
+        if (!isLongClickable) return
+        setAllColors(
+            colorEvaluator.evaluate(
+                effectProgress,
+                initialLongPressProperties?.backgroundColor ?: 0,
+                finalLongPressProperties?.backgroundColor ?: 0,
+            ) as Int,
+            colorEvaluator.evaluate(
+                effectProgress,
+                initialLongPressProperties?.labelColor ?: 0,
+                finalLongPressProperties?.labelColor ?: 0,
+            ) as Int,
+            colorEvaluator.evaluate(
+                effectProgress,
+                initialLongPressProperties?.secondaryLabelColor ?: 0,
+                finalLongPressProperties?.secondaryLabelColor ?: 0,
+            ) as Int,
+            colorEvaluator.evaluate(
+                effectProgress,
+                initialLongPressProperties?.chevronColor ?: 0,
+                finalLongPressProperties?.chevronColor ?: 0,
+            ) as Int,
+            colorEvaluator.evaluate(
+                effectProgress,
+                initialLongPressProperties?.overlayColor ?: 0,
+                finalLongPressProperties?.overlayColor ?: 0,
+            ) as Int,
+        )
+        icon.setTint(
+            icon.mIcon as ImageView,
+            colorEvaluator.evaluate(
+                effectProgress,
+                initialLongPressProperties?.iconColor ?: 0,
+                finalLongPressProperties?.iconColor ?: 0,
+            ) as Int,
+        )
+
+        val newScaleX =
+            interpolateFloat(
+                effectProgress,
+                initialLongPressProperties?.xScale ?: 1f,
+                finalLongPressProperties?.xScale ?: 1f,
+            )
+        val newScaleY =
+            interpolateFloat(
+                effectProgress,
+                initialLongPressProperties?.xScale ?: 1f,
+                finalLongPressProperties?.xScale ?: 1f,
+            )
+        val newRadius =
+            interpolateFloat(
+                effectProgress,
+                initialLongPressProperties?.cornerRadius ?: 0f,
+                finalLongPressProperties?.cornerRadius ?: 0f,
+            )
+        scaleX = newScaleX
+        scaleY = newScaleY
+        for (child in children) {
+            child.scaleX = 1f / newScaleX
+            child.scaleY = 1f / newScaleY
+        }
+        changeCornerRadius(newRadius)
+    }
+
+    private fun interpolateFloat(fraction: Float, start: Float, end: Float): Float =
+        start + fraction * (end - start)
+
+    private fun resetLongPressEffectProperties() {
+        scaleY = 1f
+        scaleX = 1f
+        for (child in children) {
+            child.scaleY = 1f
+            child.scaleX = 1f
+        }
+        changeCornerRadius(resources.getDimensionPixelSize(R.dimen.qs_corner_radius).toFloat())
+        setAllColors(
+            getBackgroundColorForState(lastState, lastDisabledByPolicy),
+            getLabelColorForState(lastState, lastDisabledByPolicy),
+            getSecondaryLabelColorForState(lastState, lastDisabledByPolicy),
+            getChevronColorForState(lastState, lastDisabledByPolicy),
+            getOverlayColorForState(lastState),
+        )
+        icon.setTint(icon.mIcon as ImageView, lastIconTint)
+    }
+
+    private fun initializeLongPressEffect() {
+        initializeLongPressProperties()
+        longPressEffect =
+            QSLongPressEffect(
+                vibratorHelper,
+                ViewConfiguration.getLongPressTimeout() - ViewConfiguration.getTapTimeout(),
+            )
+    }
+
+    private fun initializeLongPressProperties() {
+        initialLongPressProperties =
+            QSLongPressProperties(
+                /* xScale= */1f,
+                /* yScale= */1f,
+                resources.getDimensionPixelSize(R.dimen.qs_corner_radius).toFloat(),
+                getBackgroundColorForState(lastState),
+                getLabelColorForState(lastState),
+                getSecondaryLabelColorForState(lastState),
+                getChevronColorForState(lastState),
+                getOverlayColorForState(lastState),
+                lastIconTint,
+            )
+
+        finalLongPressProperties =
+            QSLongPressProperties(
+                /* xScale= */1.1f,
+                /* yScale= */1.2f,
+                resources.getDimensionPixelSize(R.dimen.qs_corner_radius).toFloat() - 20,
+                getBackgroundColorForState(Tile.STATE_ACTIVE),
+                getLabelColorForState(Tile.STATE_ACTIVE),
+                getSecondaryLabelColorForState(Tile.STATE_ACTIVE),
+                getChevronColorForState(Tile.STATE_ACTIVE),
+                getOverlayColorForState(Tile.STATE_ACTIVE),
+                Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive),
+            )
+    }
+
+    private fun changeCornerRadius(radius: Float) {
+        for (i in 0 until backgroundDrawable.numberOfLayers) {
+            val layer = backgroundDrawable.getDrawable(i)
+            if (layer is GradientDrawable) {
+                layer.cornerRadius = radius
+            }
+        }
+    }
+
     @VisibleForTesting
     internal fun getCurrentColors(): List<Int> = listOf(
             backgroundColor,
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/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
index 32deb30..6b654be 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -34,11 +34,11 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.settingslib.drawable.CircleFramedDrawable;
-import com.android.systemui.res.R;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.qs.PseudoGridView;
 import com.android.systemui.qs.QSUserSwitcherEvent;
 import com.android.systemui.qs.user.UserSwitchDialogController;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -186,7 +186,7 @@
                     (UserRecord) view.getTag();
             if (userRecord.isDisabledByAdmin()) {
                 final Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
-                        mContext, userRecord.enforcedAdmin);
+                        userRecord.enforcedAdmin);
                 mController.startActivity(intent);
             } else if (userRecord.isSwitchToEnabled) {
                 MetricsLogger.action(mContext, MetricsEvent.QS_SWITCH_USER);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt
index d1f8945..87b89ea 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt
@@ -96,10 +96,7 @@
             is PolicyResult.TileEnabled -> false
             is PolicyResult.TileDisabled -> {
                 val intent =
-                    RestrictedLockUtils.getShowAdminSupportDetailsIntent(
-                        context,
-                        policyResult.admin
-                    )
+                    RestrictedLockUtils.getShowAdminSupportDetailsIntent(policyResult.admin)
                 activityStarter.postStartActivityDismissingKeyguard(intent, 0)
                 true
             }
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index 0d9b702..7009816 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -19,6 +19,7 @@
 import android.app.NotificationManager
 import android.content.Context
 import android.content.Intent
+import android.content.pm.LauncherApps
 import android.content.res.Resources
 import android.net.Uri
 import android.os.Handler
@@ -26,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
@@ -53,7 +56,9 @@
     uiEventLogger: UiEventLogger,
     notificationManager: NotificationManager,
     userContextProvider: UserContextProvider,
-    keyguardDismissUtil: KeyguardDismissUtil
+    keyguardDismissUtil: KeyguardDismissUtil,
+    private val dialogTransitionAnimator: DialogTransitionAnimator,
+    private val panelInteractor: PanelInteractor,
 ) :
     RecordingService(
         controller,
@@ -93,10 +98,16 @@
             }
             ACTION_STOP,
             ACTION_STOP_NOTIF -> {
+                // ViewCapture needs to save it's data before it is disabled, or else the data will
+                // be lost. This is expected to change in the near future, and when that happens
+                // this line should be removed.
+                getSystemService(LauncherApps::class.java)?.saveViewCaptureData()
                 TraceUtils.traceStop(contentResolver)
             }
             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
@@ -119,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/screenshot/LegacyScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
index f01e9be..1f6d212 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
@@ -21,9 +21,7 @@
 import android.content.Context
 import android.graphics.Bitmap
 import android.graphics.Rect
-import android.graphics.drawable.Drawable
 import android.util.Log
-import android.view.Display
 import android.view.KeyEvent
 import android.view.LayoutInflater
 import android.view.ScrollCaptureResponse
@@ -32,45 +30,53 @@
 import android.view.WindowInsets
 import android.window.OnBackInvokedCallback
 import android.window.OnBackInvokedDispatcher
+import androidx.appcompat.content.res.AppCompatResources
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.res.R
 import com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS
 import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 
 /**
  * Legacy implementation of screenshot view methods. Just proxies the calls down into the original
  * ScreenshotView.
  */
-class LegacyScreenshotViewProxy(context: Context, private val logger: UiEventLogger) :
-    ScreenshotViewProxy {
+class LegacyScreenshotViewProxy
+@AssistedInject
+constructor(
+    private val logger: UiEventLogger,
+    flags: FeatureFlags,
+    @Assisted private val context: Context,
+    @Assisted private val displayId: Int
+) : ScreenshotViewProxy {
     override val view: ScreenshotView =
         LayoutInflater.from(context).inflate(R.layout.screenshot, null) as ScreenshotView
     override val screenshotPreview: View
-
-    override var defaultDisplay: Int = Display.DEFAULT_DISPLAY
-        set(value) {
-            view.setDefaultDisplay(value)
-        }
-    override var defaultTimeoutMillis: Long = 6000
-        set(value) {
-            view.setDefaultTimeoutMillis(value)
-        }
-    override var flags: FeatureFlags? = null
-        set(value) {
-            view.setFlags(value)
-        }
     override var packageName: String = ""
         set(value) {
+            field = value
             view.setPackageName(value)
         }
     override var callbacks: ScreenshotView.ScreenshotViewCallback? = null
         set(value) {
+            field = value
             view.setCallbacks(value)
         }
     override var screenshot: ScreenshotData? = null
         set(value) {
-            view.setScreenshot(value)
+            field = value
+            value?.let {
+                val badgeBg =
+                    AppCompatResources.getDrawable(context, R.drawable.overlay_badge_background)
+                val user = it.userHandle
+                if (badgeBg != null && user != null) {
+                    view.badgeScreenshot(context.packageManager.getUserBadgedIcon(badgeBg, user))
+                }
+                view.setScreenshot(it)
+            }
         }
 
     override val isAttachedToWindow
@@ -82,6 +88,8 @@
 
     init {
         view.setUiEventLogger(logger)
+        view.setDefaultDisplay(displayId)
+        view.setFlags(flags)
         addPredictiveBackListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
         setOnKeyListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
         if (LogConfig.DEBUG_WINDOW) {
@@ -95,8 +103,6 @@
     override fun updateInsets(insets: WindowInsets) = view.updateInsets(insets)
     override fun updateOrientation(insets: WindowInsets) = view.updateOrientation(insets)
 
-    override fun badgeScreenshot(userBadgedIcon: Drawable) = view.badgeScreenshot(userBadgedIcon)
-
     override fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator =
         view.createScreenshotDropInAnimation(screenRect, showFlash)
 
@@ -130,14 +136,17 @@
         response: ScrollCaptureResponse,
         screenBitmap: Bitmap,
         newScreenshot: Bitmap,
-        screenshotTakenInPortrait: Boolean
-    ) =
+        screenshotTakenInPortrait: Boolean,
+        onTransitionPrepared: Runnable,
+    ) {
         view.prepareScrollingTransition(
             response,
             screenBitmap,
             newScreenshot,
             screenshotTakenInPortrait
         )
+        view.post { onTransitionPrepared.run() }
+    }
 
     override fun startLongScreenshotTransition(
         transitionDestination: Rect,
@@ -155,10 +164,19 @@
 
     override fun announceForAccessibility(string: String) = view.announceForAccessibility(string)
 
-    override fun getViewTreeObserver(): ViewTreeObserver = view.viewTreeObserver
-
-    override fun post(runnable: Runnable) {
-        view.post(runnable)
+    override fun prepareEntranceAnimation(runnable: Runnable) {
+        view.viewTreeObserver.addOnPreDrawListener(
+            object : ViewTreeObserver.OnPreDrawListener {
+                override fun onPreDraw(): Boolean {
+                    if (LogConfig.DEBUG_WINDOW) {
+                        Log.d(TAG, "onPreDraw: startAnimation")
+                    }
+                    view.viewTreeObserver.removeOnPreDrawListener(this)
+                    runnable.run()
+                    return true
+                }
+            }
+        )
     }
 
     private fun addPredictiveBackListener(onDismissRequested: (ScreenshotEvent) -> Unit) {
@@ -166,7 +184,7 @@
             if (LogConfig.DEBUG_INPUT) {
                 Log.d(TAG, "Predictive Back callback dispatched")
             }
-            onDismissRequested.invoke(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER)
+            onDismissRequested.invoke(SCREENSHOT_DISMISSED_OTHER)
         }
         view.addOnAttachStateChangeListener(
             object : View.OnAttachStateChangeListener {
@@ -201,7 +219,7 @@
                         if (LogConfig.DEBUG_INPUT) {
                             Log.d(TAG, "onKeyEvent: $keyCode")
                         }
-                        onDismissRequested.invoke(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER)
+                        onDismissRequested.invoke(SCREENSHOT_DISMISSED_OTHER)
                         return true
                     }
                     return false
@@ -210,10 +228,9 @@
         )
     }
 
-    class Factory : ScreenshotViewProxy.Factory {
-        override fun getProxy(context: Context, logger: UiEventLogger): ScreenshotViewProxy {
-            return LegacyScreenshotViewProxy(context, logger)
-        }
+    @AssistedFactory
+    interface Factory : ScreenshotViewProxy.Factory {
+        override fun getProxy(context: Context, displayId: Int): LegacyScreenshotViewProxy
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 6bab956..198a29c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -228,7 +228,7 @@
     // From WizardManagerHelper.java
     private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete";
 
-    private static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000;
+    static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000;
 
     private final WindowContext mContext;
     private final FeatureFlags mFlags;
@@ -344,7 +344,7 @@
         mMessageContainerController = messageContainerController;
         mAssistContentRequester = assistContentRequester;
 
-        mViewProxy = viewProxyFactory.getProxy(mContext, mUiEventLogger);
+        mViewProxy = viewProxyFactory.getProxy(mContext, mDisplayId);
 
         mScreenshotHandler.setOnTimeoutRunnable(() -> {
             if (DEBUG_UI) {
@@ -460,7 +460,7 @@
 
         attachWindow();
 
-        boolean showFlash = true;
+        boolean showFlash;
         if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) {
             if (screenshot.getScreenBounds() != null
                     && aspectRatiosMatch(screenshot.getBitmap(), screenshot.getInsets(),
@@ -472,15 +472,14 @@
                 screenshot.setScreenBounds(new Rect(0, 0, screenshot.getBitmap().getWidth(),
                         screenshot.getBitmap().getHeight()));
             }
+        } else {
+            showFlash = true;
         }
 
-        prepareAnimation(screenshot.getScreenBounds(), showFlash, () -> {
-            mMessageContainerController.onScreenshotTaken(screenshot);
-        });
+        mViewProxy.prepareEntranceAnimation(
+                () -> startAnimation(screenshot.getScreenBounds(), showFlash,
+                        () -> mMessageContainerController.onScreenshotTaken(screenshot)));
 
-        mViewProxy.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon(
-                mContext.getDrawable(R.drawable.overlay_badge_background),
-                screenshot.getUserHandle()));
         mViewProxy.setScreenshot(screenshot);
 
         // ignore system bar insets for the purpose of window layout
@@ -596,9 +595,6 @@
                 setWindowFocusable(false);
             }
         });
-        mViewProxy.setFlags(mFlags);
-        mViewProxy.setDefaultDisplay(mDisplayId);
-        mViewProxy.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis());
 
         if (DEBUG_WINDOW) {
             Log.d(TAG, "setContentView: " + mViewProxy.getView());
@@ -606,22 +602,6 @@
         setContentView(mViewProxy.getView());
     }
 
-    private void prepareAnimation(Rect screenRect, boolean showFlash,
-            Runnable onAnimationComplete) {
-        mViewProxy.getViewTreeObserver().addOnPreDrawListener(
-                new ViewTreeObserver.OnPreDrawListener() {
-                    @Override
-                    public boolean onPreDraw() {
-                        if (DEBUG_WINDOW) {
-                            Log.d(TAG, "onPreDraw: startAnimation");
-                        }
-                        mViewProxy.getViewTreeObserver().removeOnPreDrawListener(this);
-                        startAnimation(screenRect, showFlash, onAnimationComplete);
-                        return true;
-                    }
-                });
-    }
-
     private void enqueueScrollCaptureRequest(UserHandle owner) {
         // Wait until this window is attached to request because it is
         // the reference used to locate the target window (below).
@@ -706,10 +686,14 @@
                 Bitmap newScreenshot = mImageCapture.captureDisplay(mDisplayId,
                         new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels));
 
-                mViewProxy.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
-                        mScreenshotTakenInPortrait);
-                // delay starting scroll capture to make sure the scrim is up before the app moves
-                mViewProxy.post(() -> runBatchScrollCapture(response, owner));
+                if (newScreenshot != null) {
+                    // delay starting scroll capture to make sure scrim is up before the app moves
+                    mViewProxy.prepareScrollingTransition(
+                            response, mScreenBitmap, newScreenshot, mScreenshotTakenInPortrait,
+                            () -> runBatchScrollCapture(response, owner));
+                } else {
+                    Log.wtf(TAG, "failed to capture current screenshot for scroll transition");
+                }
             });
         } catch (InterruptedException | ExecutionException e) {
             Log.e(TAG, "requestScrollCapture failed", e);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 8a8766d..1c5a8a1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -26,6 +26,7 @@
 import static com.android.systemui.screenshot.LogConfig.DEBUG_UI;
 import static com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW;
 import static com.android.systemui.screenshot.LogConfig.logTag;
+import static com.android.systemui.screenshot.ScreenshotController.SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS;
 
 import static java.util.Objects.requireNonNull;
 
@@ -33,6 +34,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.BroadcastOptions;
 import android.app.Notification;
@@ -168,7 +170,6 @@
     private ScreenshotData mScreenshotData;
 
     private final InteractionJankMonitor mInteractionJankMonitor;
-    private long mDefaultTimeoutOfTimeoutHandler;
     private FeatureFlags mFlags;
     private final Bundle mInteractiveBroadcastOption;
 
@@ -244,10 +245,6 @@
         return InteractionJankMonitor.getInstance();
     }
 
-    void setDefaultTimeoutMillis(long timeout) {
-        mDefaultTimeoutOfTimeoutHandler = timeout;
-    }
-
     public void hideScrollChip() {
         mScrollChip.setVisibility(View.GONE);
     }
@@ -755,7 +752,7 @@
                         InteractionJankMonitor.Configuration.Builder.withView(
                                         CUJ_TAKE_SCREENSHOT, mScreenshotStatic)
                                 .setTag("Actions")
-                                .setTimeout(mDefaultTimeoutOfTimeoutHandler);
+                                .setTimeout(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS);
                 mInteractionJankMonitor.begin(builder);
             }
         });
@@ -781,7 +778,7 @@
         return animator;
     }
 
-    void badgeScreenshot(Drawable badge) {
+    void badgeScreenshot(@Nullable Drawable badge) {
         mScreenshotBadge.setImageDrawable(badge);
         mScreenshotBadge.setVisibility(badge != null ? View.VISIBLE : View.GONE);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
index d5c7f95..182b889 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
@@ -21,23 +21,16 @@
 import android.content.Context
 import android.graphics.Bitmap
 import android.graphics.Rect
-import android.graphics.drawable.Drawable
 import android.view.ScrollCaptureResponse
 import android.view.View
 import android.view.ViewGroup
-import android.view.ViewTreeObserver
 import android.view.WindowInsets
-import com.android.internal.logging.UiEventLogger
-import com.android.systemui.flags.FeatureFlags
 
 /** Abstraction of the surface between ScreenshotController and ScreenshotView */
 interface ScreenshotViewProxy {
     val view: ViewGroup
     val screenshotPreview: View
 
-    var defaultDisplay: Int
-    var defaultTimeoutMillis: Long
-    var flags: FeatureFlags?
     var packageName: String
     var callbacks: ScreenshotView.ScreenshotViewCallback?
     var screenshot: ScreenshotData?
@@ -49,7 +42,6 @@
     fun reset()
     fun updateInsets(insets: WindowInsets)
     fun updateOrientation(insets: WindowInsets)
-    fun badgeScreenshot(userBadgedIcon: Drawable)
     fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator
     fun addQuickShareChip(quickShareAction: Notification.Action)
     fun setChipIntents(imageData: ScreenshotController.SavedImageData)
@@ -61,7 +53,8 @@
         response: ScrollCaptureResponse,
         screenBitmap: Bitmap,
         newScreenshot: Bitmap,
-        screenshotTakenInPortrait: Boolean
+        screenshotTakenInPortrait: Boolean,
+        onTransitionPrepared: Runnable,
     )
     fun startLongScreenshotTransition(
         transitionDestination: Rect,
@@ -73,10 +66,9 @@
     fun stopInputListening()
     fun requestFocus()
     fun announceForAccessibility(string: String)
-    fun getViewTreeObserver(): ViewTreeObserver
-    fun post(runnable: Runnable)
+    fun prepareEntranceAnimation(runnable: Runnable)
 
     interface Factory {
-        fun getProxy(context: Context, logger: UiEventLogger): ScreenshotViewProxy
+        fun getProxy(context: Context, displayId: Int): ScreenshotViewProxy
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java b/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java
index 71c2cb4..5df6c45 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java
@@ -40,7 +40,7 @@
     private final Context mContext;
 
     private Runnable mOnTimeout;
-    private int mDefaultTimeout = DEFAULT_TIMEOUT_MILLIS;
+    int mDefaultTimeout = DEFAULT_TIMEOUT_MILLIS;
 
     @Inject
     public TimeoutHandler(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index a00c81d..cdb9abb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -86,7 +86,8 @@
             ScreenshotSoundControllerImpl screenshotSoundProviderImpl);
 
     @Provides
-    static ScreenshotViewProxy.Factory providesScreenshotViewProxyFactory() {
-        return new LegacyScreenshotViewProxy.Factory();
+    static ScreenshotViewProxy.Factory providesScreenshotViewProxyFactory(
+            LegacyScreenshotViewProxy.Factory legacyScreenshotViewProxyFactory) {
+        return legacyScreenshotViewProxyFactory;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
index 861a2ed..539b0c2 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -19,6 +19,7 @@
 import static com.android.systemui.Flags.hapticBrightnessSlider;
 
 import android.content.Context;
+import android.content.Intent;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
@@ -33,6 +34,7 @@
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.haptics.slider.HapticSliderViewBinder;
 import com.android.systemui.haptics.slider.SeekableSliderHapticPlugin;
+import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.VibratorHelper;
@@ -62,6 +64,7 @@
     private final UiEventLogger mUiEventLogger;
 
     private final SeekableSliderHapticPlugin mBrightnessSliderHapticPlugin;
+    private final ActivityStarter mActivityStarter;
 
     private final Gefingerpoken mOnInterceptListener = new Gefingerpoken() {
         @Override
@@ -84,11 +87,13 @@
             BrightnessSliderView brightnessSliderView,
             FalsingManager falsingManager,
             UiEventLogger uiEventLogger,
-            SeekableSliderHapticPlugin brightnessSliderHapticPlugin) {
+            SeekableSliderHapticPlugin brightnessSliderHapticPlugin,
+            ActivityStarter activityStarter) {
         super(brightnessSliderView);
         mFalsingManager = falsingManager;
         mUiEventLogger = uiEventLogger;
         mBrightnessSliderHapticPlugin = brightnessSliderHapticPlugin;
+        mActivityStarter = activityStarter;
     }
 
     /**
@@ -131,7 +136,15 @@
 
     @Override
     public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
-        mView.setEnforcedAdmin(admin);
+        if (admin == null) {
+            mView.setAdminBlocker(null);
+        } else {
+            mView.setAdminBlocker(() -> {
+                Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(admin);
+                mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
+                return true;
+            });
+        }
     }
 
     private void setMirror(ToggleSlider toggleSlider) {
@@ -259,18 +272,21 @@
         private final UiEventLogger mUiEventLogger;
         private final VibratorHelper mVibratorHelper;
         private final SystemClock mSystemClock;
+        private final ActivityStarter mActivityStarter;
 
         @Inject
         public Factory(
                 FalsingManager falsingManager,
                 UiEventLogger uiEventLogger,
                 VibratorHelper vibratorHelper,
-                SystemClock clock
+                SystemClock clock,
+                ActivityStarter activityStarter
         ) {
             mFalsingManager = falsingManager;
             mUiEventLogger = uiEventLogger;
             mVibratorHelper = vibratorHelper;
             mSystemClock = clock;
+            mActivityStarter = activityStarter;
         }
 
         /**
@@ -292,7 +308,8 @@
             if (hapticBrightnessSlider()) {
                 HapticSliderViewBinder.bind(viewRoot, plugin);
             }
-            return new BrightnessSliderController(root, mFalsingManager, mUiEventLogger, plugin);
+            return new BrightnessSliderController(
+                    root, mFalsingManager, mUiEventLogger, plugin, mActivityStarter);
         }
 
         /** Get the layout to inflate based on what slider to use */
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
index c43d20c..92006a4 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
@@ -31,7 +31,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.settingslib.RestrictedLockUtils;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.res.R;
 
@@ -120,9 +119,8 @@
      * @param admin
      * @see ToggleSeekBar#setEnforcedAdmin
      */
-    public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
-        mSlider.setEnabled(admin == null);
-        mSlider.setEnforcedAdmin(admin);
+    void setAdminBlocker(ToggleSeekBar.AdminBlocker blocker) {
+        mSlider.setAdminBlocker(blocker);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
index a5a0ae7..288ff09 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
@@ -17,20 +17,15 @@
 package com.android.systemui.settings.brightness;
 
 import android.content.Context;
-import android.content.Intent;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.SeekBar;
 
-import com.android.settingslib.RestrictedLockUtils;
-import com.android.systemui.Dependency;
-import com.android.systemui.plugins.ActivityStarter;
-
 public class ToggleSeekBar extends SeekBar {
     private String mAccessibilityLabel;
 
-    private RestrictedLockUtils.EnforcedAdmin mEnforcedAdmin = null;
+    private AdminBlocker mAdminBlocker;
 
     public ToggleSeekBar(Context context) {
         super(context);
@@ -46,10 +41,7 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
-        if (mEnforcedAdmin != null) {
-            Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
-                    mContext, mEnforcedAdmin);
-            Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(intent, 0);
+        if (mAdminBlocker != null && mAdminBlocker.block()) {
             return true;
         }
         if (!isEnabled()) {
@@ -71,7 +63,12 @@
         }
     }
 
-    public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
-        mEnforcedAdmin = admin;
+    void setAdminBlocker(AdminBlocker blocker) {
+        mAdminBlocker = blocker;
+        setEnabled(blocker == null);
+    }
+
+    interface AdminBlocker {
+        boolean block();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index b867550..8b791de 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1753,10 +1753,11 @@
     }
 
     private void updateKeyguardStatusViewAlignment(boolean animate) {
+        boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered();
         if (migrateClocksToBlueprint()) {
+            mKeyguardInteractor.setClockShouldBeCentered(shouldBeCentered);
             return;
         }
-        boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered();
         ConstraintLayout layout = mNotificationContainerParent;
         mKeyguardStatusViewController.updateAlignment(
                 layout, mSplitShadeEnabled, shouldBeCentered, animate);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index de21a73..a343ded 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 the shade is in the process of collapsing. */
-    val isCollapsing: Boolean
-
     /** Returns whether shade's height is zero. */
     val isFullyCollapsed: Boolean
 
@@ -102,19 +99,6 @@
     fun showAodUi()
 
     /**
-     * This method should not be used anymore, you should probably use [.isShadeFullyOpen] instead.
-     * It was overused as indicating if shade is open or we're on keyguard/AOD. Moving forward we
-     * should be explicit about the what state we're checking.
-     *
-     * @return if panel is covering the screen, which means we're in expanded shade or keyguard/AOD
-     */
-    @Deprecated(
-        "depends on the state you check, use {@link #isShadeFullyExpanded()},\n" +
-            "{@link #isOnAod()}, {@link #isOnKeyguard()} instead."
-    )
-    fun isFullyExpanded(): Boolean
-
-    /**
      * Sends an external (e.g. Status Bar) touch event to the Shade touch handler.
      *
      * This is different from [startInputFocusTransfer] as it doesn't rely on setting the launcher
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
index b67156f..c9140b5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -33,31 +33,33 @@
     ShadeBackActionInteractor,
     ShadeLockscreenInteractor,
     PanelExpansionInteractor {
-    override fun expandToNotifications() {}
-    override val isExpanded: Boolean = false
+    @Deprecated("Use ShadeInteractor instead") override fun expandToNotifications() {}
+    @Deprecated("Use ShadeInteractor instead") override val isExpanded: Boolean = false
     override val isPanelExpanded: Boolean = false
     override fun animateCollapseQs(fullyCollapse: Boolean) {}
     override fun canBeCollapsed(): Boolean = false
-    override val isCollapsing: Boolean = false
+    @Deprecated("Use ShadeAnimationInteractor instead") override val isCollapsing: Boolean = false
     override val isFullyCollapsed: Boolean = false
     override val isTracking: Boolean = false
     override val isViewEnabled: Boolean = false
     override fun shouldHideStatusBarIconsWhenExpanded() = false
-    override fun blockExpansionForCurrentTouch() {}
+    @Deprecated("Not supported by scenes") override fun blockExpansionForCurrentTouch() {}
     override fun disableHeader(state1: Int, state2: Int, animated: Boolean) {}
     override fun startExpandLatencyTracking() {}
     override fun startBouncerPreHideAnimation() {}
     override fun dozeTimeTick() {}
     override fun resetViews(animate: Boolean) {}
     override val barState: Int = 0
+    @Deprecated("Only supported by very old devices that will not adopt scenes.")
     override fun closeUserSwitcherIfOpen(): Boolean {
         return false
     }
     override fun onBackPressed() {}
+    @Deprecated("According to b/318376223, shade predictive back is not be supported.")
     override fun onBackProgressed(progressFraction: Float) {}
     override fun setAlpha(alpha: Int, animate: Boolean) {}
     override fun setAlphaChangeAnimationEndAction(r: Runnable) {}
-    override fun setPulsing(pulsing: Boolean) {}
+    @Deprecated("Not supported by scenes") override fun setPulsing(pulsing: Boolean) {}
     override fun setQsScrimEnabled(qsScrimEnabled: Boolean) {}
     override fun setAmbientIndicationTop(ambientIndicationTop: Int, ambientTextVisible: Boolean) {}
     override fun updateSystemUiStateFlags() {}
@@ -66,14 +68,18 @@
     override fun removeOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener) {}
     override fun transitionToExpandedShade(delay: Long) {}
 
-    override fun resetViewGroupFade() {}
+    @Deprecated("Not supported by scenes") override fun resetViewGroupFade() {}
+    @Deprecated("Not supported by scenes")
     override fun setKeyguardTransitionProgress(keyguardAlpha: Float, keyguardTranslationY: Int) {}
-    override fun setOverStretchAmount(amount: Float) {}
+    @Deprecated("Not supported by scenes") override fun setOverStretchAmount(amount: Float) {}
+    @Deprecated("TODO(b/325072511) delete this")
     override fun setKeyguardStatusBarAlpha(alpha: Float) {}
     override fun showAodUi() {}
-    override fun isFullyExpanded(): Boolean {
-        return false
-    }
+    @Deprecated(
+        "depends on the state you check, use {@link #isShadeFullyExpanded()},\n" +
+            "{@link #isOnAod()}, {@link #isOnKeyguard()} instead."
+    )
+    override val isFullyExpanded = false
     override fun handleExternalTouch(event: MotionEvent): Boolean {
         return false
     }
@@ -84,6 +90,7 @@
 
     override val shadeHeadsUpTracker = ShadeHeadsUpTrackerEmptyImpl()
     override val shadeFoldAnimator = ShadeFoldAnimatorEmptyImpl()
+    @Deprecated("Use SceneInteractor.currentScene instead.")
     override val legacyPanelExpansion = flowOf(0f)
 }
 
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 01118bd..bd96a33 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
@@ -41,4 +41,20 @@
      * backwards-compatibility and should not be consumed by newer code.
      */
     @Deprecated("Use SceneInteractor.currentScene instead.") val legacyPanelExpansion: Flow<Float>
+
+    /**
+     * This method should not be used anymore, you should probably use [.isShadeFullyOpen] instead.
+     * It was overused as indicating if shade is open or we're on keyguard/AOD. Moving forward we
+     * should be explicit about the what state we're checking.
+     *
+     * @return if panel is covering the screen, which means we're in expanded shade or keyguard/AOD
+     */
+    @Deprecated(
+        "depends on the state you check, use {@link #isShadeFullyExpanded()},\n" +
+            "{@link #isOnAod()}, {@link #isOnKeyguard()} instead."
+    )
+    val isFullyExpanded: 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 20f73b0..c5a6968 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
@@ -35,6 +35,8 @@
 @Inject
 constructor(
     sceneInteractor: SceneInteractor,
+    shadeInteractor: ShadeInteractor,
+    shadeAnimationInteractor: ShadeAnimationInteractor,
 ) : PanelExpansionInteractor {
 
     /**
@@ -93,6 +95,16 @@
             }
         }
 
+    @Deprecated(
+        "depends on the state you check, use {@link #isShadeFullyExpanded()},\n" +
+            "{@link #isOnAod()}, {@link #isOnKeyguard()} instead."
+    )
+    override val isFullyExpanded = shadeInteractor.isAnyFullyExpanded.value
+
+    @Deprecated("Use ShadeAnimationInteractor instead")
+    override val isCollapsing =
+        shadeAnimationInteractor.isAnyCloseAnimationRunning.value ||
+            shadeAnimationInteractor.isLaunchingActivity.value
     private fun SceneKey.isExpandable(): Boolean {
         return this == Scenes.Shade || this == Scenes.QuickSettings
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt
index 5a777e8..134c983 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.shade.domain.interactor
 
 import com.android.systemui.shade.data.repository.ShadeAnimationRepository
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 
@@ -38,5 +37,5 @@
      * completes the close. Important: if QS is collapsing back to shade, this will be false because
      * that is not considered "closing".
      */
-    abstract val isAnyCloseAnimationRunning: Flow<Boolean>
+    abstract val isAnyCloseAnimationRunning: StateFlow<Boolean>
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt
index 2a7658a..f364d6d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt
@@ -19,7 +19,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.shade.data.repository.ShadeAnimationRepository
 import javax.inject.Inject
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.MutableStateFlow
 
 /** Implementation of ShadeAnimationInteractor for shadeless SysUI variants. */
 @SysUISingleton
@@ -28,5 +28,5 @@
 constructor(
     shadeAnimationRepository: ShadeAnimationRepository,
 ) : ShadeAnimationInteractor(shadeAnimationRepository) {
-    override val isAnyCloseAnimationRunning = flowOf(false)
+    override val isAnyCloseAnimationRunning = MutableStateFlow(false)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt
index eaac8ae..d9982e3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt
@@ -18,21 +18,26 @@
 
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.data.repository.ShadeAnimationRepository
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
 
 /** Implementation of ShadeAnimationInteractor compatible with the scene container framework. */
 @SysUISingleton
 class ShadeAnimationInteractorSceneContainerImpl
 @Inject
 constructor(
+    @Background scope: CoroutineScope,
     shadeAnimationRepository: ShadeAnimationRepository,
     sceneInteractor: SceneInteractor,
 ) : ShadeAnimationInteractor(shadeAnimationRepository) {
@@ -56,4 +61,5 @@
                 }
             }
             .distinctUntilChanged()
+            .stateIn(scope, SharingStarted.Eagerly, false)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 6155348..5171a5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -39,8 +39,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.SystemBarUtils;
 import com.android.systemui.animation.ShadeInterpolation;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.flags.RefactorFlag;
 import com.android.systemui.res.R;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.notification.ColorUpdateLogger;
@@ -95,8 +93,6 @@
     private float mCornerAnimationDistance;
     private float mActualWidth = -1;
     private int mMaxIconsOnLockscreen;
-    private final RefactorFlag mSensitiveRevealAnim =
-            RefactorFlag.forView(Flags.SENSITIVE_REVEAL_ANIM);
     private boolean mCanModifyColorOfNotifications;
     private boolean mCanInteract;
     private NotificationStackScrollLayout mHostLayout;
@@ -266,7 +262,7 @@
         }
 
         final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight();
-        if (mSensitiveRevealAnim.isEnabled() && viewState.hidden) {
+        if (viewState.hidden) {
             // if the shelf is hidden, position it at the end of the stack (plus the clip
             // padding), such that when it appears animated, it will smoothly move in from the
             // bottom, without jump cutting any notifications
@@ -398,10 +394,6 @@
         //  find the first view that doesn't overlap with the shelf
         int notGoneIndex = 0;
         int colorOfViewBeforeLast = NO_COLOR;
-        boolean backgroundForceHidden = false;
-        if (mHideBackground && !((ShelfState) getViewState()).hasItemsInStableShelf) {
-            backgroundForceHidden = true;
-        }
         int colorTwoBefore = NO_COLOR;
         int previousColor = NO_COLOR;
         float transitionAmount = 0.0f;
@@ -429,8 +421,7 @@
                     expandingAnimated, isLastChild, shelfClipStart);
 
             // TODO(b/172289889) scale mPaddingBetweenElements with expansion amount
-            if ((!mSensitiveRevealAnim.isEnabled() && ((isLastChild && !child.isInShelf())
-                    || backgroundForceHidden)) || aboveShelf) {
+            if (aboveShelf) {
                 notificationClipEnd = shelfStart + getIntrinsicHeight();
             } else {
                 notificationClipEnd = shelfStart - mPaddingBetweenElements;
@@ -440,8 +431,7 @@
 
             // If the current row is an ExpandableNotificationRow, update its color, roundedness,
             // and icon state.
-            if (child instanceof ExpandableNotificationRow) {
-                ExpandableNotificationRow expandableRow = (ExpandableNotificationRow) child;
+            if (child instanceof ExpandableNotificationRow expandableRow) {
                 numViewsInShelf += inShelfAmount;
                 int ownColorUntinted = expandableRow.getBackgroundColorWithoutTint();
                 if (viewStart >= shelfStart && mNotGoneIndex == -1) {
@@ -471,16 +461,8 @@
                 notGoneIndex++;
             }
 
-            if (child instanceof ActivatableNotificationView) {
-                ActivatableNotificationView anv = (ActivatableNotificationView) child;
-                // Because we show whole notifications on the lockscreen, the bottom notification is
-                // always "just about to enter the shelf" by normal scrolling rules.  This is fine
-                // if the shelf is visible, but if the shelf is hidden, it causes incorrect curling.
-                // notificationClipEnd handles the discrepancy between a visible and hidden shelf,
-                // so we use that when on the keyguard (and while animating away) to reduce curling.
-                final float keyguardSafeShelfStart = !mSensitiveRevealAnim.isEnabled()
-                        && mAmbientState.isOnKeyguard() ? notificationClipEnd : shelfStart;
-                updateCornerRoundnessOnScroll(anv, viewStart, keyguardSafeShelfStart);
+            if (child instanceof ActivatableNotificationView anv) {
+                updateCornerRoundnessOnScroll(anv, viewStart, shelfStart);
             }
         }
 
@@ -519,11 +501,10 @@
         mShelfIcons.applyIconStates();
         for (int i = 0; i < getHostLayoutChildCount(); i++) {
             View child = getHostLayoutChildAt(i);
-            if (!(child instanceof ExpandableNotificationRow)
+            if (!(child instanceof ExpandableNotificationRow row)
                     || child.getVisibility() == GONE) {
                 continue;
             }
-            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
             updateContinuousClipping(row);
         }
         boolean hideBackground = isHidden;
@@ -613,8 +594,7 @@
     private void clipTransientViews() {
         for (int i = 0; i < getHostLayoutTransientViewCount(); i++) {
             View transientView = getHostLayoutTransientView(i);
-            if (transientView instanceof ExpandableView) {
-                ExpandableView transientExpandableView = (ExpandableView) transientView;
+            if (transientView instanceof ExpandableView transientExpandableView) {
                 updateNotificationClipHeight(transientExpandableView, getTranslationY(), -1);
             }
         }
@@ -871,10 +851,9 @@
     }
 
     private void setIconTransformationAmount(ExpandableView view, float transitionAmount) {
-        if (!(view instanceof ExpandableNotificationRow)) {
+        if (!(view instanceof ExpandableNotificationRow row)) {
             return;
         }
-        ExpandableNotificationRow row = (ExpandableNotificationRow) view;
         StatusBarIconView icon = row.getShelfIcon();
         NotificationIconContainer.IconState iconState = getIconState(icon);
         if (iconState == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
index 072f56d..dcfccd8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
@@ -61,7 +61,7 @@
                 return false;
             }
 
-            if (!Flags.notificationsBackgroundMediaIcons()) {
+            if (!Flags.notificationsBackgroundIcons()) {
                 inflateOrUpdateIcons(entry);
             }
 
@@ -73,14 +73,14 @@
         @Override
         public void onEntryInit(@NonNull NotificationEntry entry) {
             // We default to STATE_ICONS_UNINFLATED anyway, so there's no need to initialize it.
-            if (!Flags.notificationsBackgroundMediaIcons()) {
+            if (!Flags.notificationsBackgroundIcons()) {
                 mIconsState.put(entry, STATE_ICONS_UNINFLATED);
             }
         }
 
         @Override
         public void onEntryAdded(@NonNull NotificationEntry entry) {
-            if (Flags.notificationsBackgroundMediaIcons()) {
+            if (Flags.notificationsBackgroundIcons()) {
                 if (isMediaNotification(entry.getSbn())) {
                     inflateOrUpdateIcons(entry);
                 }
@@ -94,7 +94,7 @@
                 mIconsState.put(entry, STATE_ICONS_UNINFLATED);
             }
 
-            if (Flags.notificationsBackgroundMediaIcons()) {
+            if (Flags.notificationsBackgroundIcons()) {
                 if (isMediaNotification(entry.getSbn())) {
                     inflateOrUpdateIcons(entry);
                 }
@@ -120,7 +120,7 @@
                 break;
             case STATE_ICONS_INFLATED:
                 try {
-                    mIconManager.updateIcons(entry);
+                    mIconManager.updateIcons(entry, /* usingCache = */ false);
                 } catch (InflationException e) {
                     reportInflationError(entry, e);
                     mIconsState.put(entry, STATE_ICONS_ERROR);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index 6400ff6..4bbe035 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -138,7 +138,7 @@
 
         if (entry.rowExists()) {
             mLogger.logUpdatingRow(entry, params);
-            mIconManager.updateIcons(entry);
+            mIconManager.updateIcons(entry, /* usingCache = */ false);
             ExpandableNotificationRow row = entry.getRow();
             row.reset();
             updateRow(entry, row);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index a5f42bb..a900e45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -28,14 +28,24 @@
 import android.widget.ImageView
 import com.android.app.tracing.traceSection
 import com.android.internal.statusbar.StatusBarIcon
+import com.android.systemui.Flags
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.notification.InflationException
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import java.util.concurrent.ConcurrentHashMap
 import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 
 /**
  * Inflates and updates icons associated with notifications
@@ -53,9 +63,18 @@
 constructor(
     private val notifCollection: CommonNotifCollection,
     private val launcherApps: LauncherApps,
-    private val iconBuilder: IconBuilder
+    private val iconBuilder: IconBuilder,
+    @Application private val applicationCoroutineScope: CoroutineScope,
+    @Background private val bgCoroutineContext: CoroutineContext,
+    @Main private val mainCoroutineContext: CoroutineContext,
 ) : ConversationIconManager {
     private var unimportantConversationKeys: Set<String> = emptySet()
+    /**
+     * A map of running jobs for fetching the person avatar from launcher. The key is the
+     * notification entry key.
+     */
+    private var launcherPeopleAvatarIconJobs: ConcurrentHashMap<String, Job> =
+        ConcurrentHashMap<String, Job>()
 
     fun attach() {
         notifCollection.addCollectionListener(entryListener)
@@ -136,13 +155,23 @@
      * @throws InflationException Exception if required icons are not valid or specified
      */
     @Throws(InflationException::class)
-    fun updateIcons(entry: NotificationEntry) =
+    fun updateIcons(entry: NotificationEntry, usingCache: Boolean = false) =
         traceSection("IconManager.updateIcons") {
             if (!entry.icons.areIconsAvailable) {
                 return@traceSection
             }
-            entry.icons.smallIconDescriptor = null
-            entry.icons.peopleAvatarDescriptor = null
+
+            if (usingCache && !Flags.notificationsBackgroundIcons()) {
+                Log.wtf(
+                    TAG,
+                    "Updating using the cache is not supported when the " +
+                        "notifications_background_conversation_icons flag is off"
+                )
+            }
+            if (!usingCache || !Flags.notificationsBackgroundIcons()) {
+                entry.icons.smallIconDescriptor = null
+                entry.icons.peopleAvatarDescriptor = null
+            }
 
             val (normalIconDescriptor, sensitiveIconDescriptor) = getIconDescriptors(entry)
             val notificationContentDescription =
@@ -188,7 +217,7 @@
     @Throws(InflationException::class)
     private fun getIconDescriptor(entry: NotificationEntry, redact: Boolean): StatusBarIcon {
         val n = entry.sbn.notification
-        val showPeopleAvatar = isImportantConversation(entry) && !redact
+        val showPeopleAvatar = !redact && isImportantConversation(entry)
 
         val peopleAvatarDescriptor = entry.icons.peopleAvatarDescriptor
         val smallIconDescriptor = entry.icons.smallIconDescriptor
@@ -208,26 +237,18 @@
             })
                 ?: throw InflationException("No icon in notification from " + entry.sbn.packageName)
 
-        val ic =
-            StatusBarIcon(
-                entry.sbn.user,
-                entry.sbn.packageName,
-                icon,
-                n.iconLevel,
-                n.number,
-                iconBuilder.getIconContentDescription(n)
-            )
+        val sbi = icon.toStatusBarIcon(entry)
 
         // Cache if important conversation.
         if (isImportantConversation(entry)) {
             if (showPeopleAvatar) {
-                entry.icons.peopleAvatarDescriptor = ic
+                entry.icons.peopleAvatarDescriptor = sbi
             } else {
-                entry.icons.smallIconDescriptor = ic
+                entry.icons.smallIconDescriptor = sbi
             }
         }
 
-        return ic
+        return sbi
     }
 
     @Throws(InflationException::class)
@@ -243,16 +264,69 @@
         }
     }
 
-    @Throws(InflationException::class)
-    private fun createPeopleAvatar(entry: NotificationEntry): Icon? {
-        var ic: Icon? = null
+    private fun Icon.toStatusBarIcon(entry: NotificationEntry): StatusBarIcon {
+        val n = entry.sbn.notification
+        return StatusBarIcon(
+            entry.sbn.user,
+            entry.sbn.packageName,
+            /* icon = */ this,
+            n.iconLevel,
+            n.number,
+            iconBuilder.getIconContentDescription(n)
+        )
+    }
 
-        val shortcut = entry.ranking.conversationShortcutInfo
-        if (shortcut != null) {
-            ic = launcherApps.getShortcutIcon(shortcut)
+    private suspend fun getLauncherShortcutIconForPeopleAvatar(entry: NotificationEntry) =
+        withContext(bgCoroutineContext) {
+            var icon: Icon? = null
+            val shortcut = entry.ranking.conversationShortcutInfo
+            if (shortcut != null) {
+                try {
+                    icon = launcherApps.getShortcutIcon(shortcut)
+                } catch (e: Exception) {
+                    Log.e(
+                        TAG,
+                        "Error calling LauncherApps#getShortcutIcon for notification $entry: $e"
+                    )
+                }
+            }
+
+            // Once we have the icon, updating it should happen on the main thread.
+            if (icon != null) {
+                withContext(mainCoroutineContext) {
+                    val iconDescriptor = icon.toStatusBarIcon(entry)
+
+                    // Cache the value
+                    entry.icons.peopleAvatarDescriptor = iconDescriptor
+
+                    // Update the icons using the cached value
+                    updateIcons(entry = entry, usingCache = true)
+                }
+            }
         }
 
-        // Fall back to extract from message
+    @Throws(InflationException::class)
+    private fun createPeopleAvatar(entry: NotificationEntry): Icon {
+        var ic: Icon? = null
+
+        if (Flags.notificationsBackgroundIcons()) {
+            // Ideally we want to get the icon from launcher, but this is a binder transaction that
+            // may take longer so let's kick it off on a background thread and use a placeholder in
+            // the meantime.
+            // Cancel the previous job if necessary.
+            launcherPeopleAvatarIconJobs[entry.key]?.cancel()
+            launcherPeopleAvatarIconJobs[entry.key] =
+                applicationCoroutineScope
+                    .launch { getLauncherShortcutIconForPeopleAvatar(entry) }
+                    .apply { invokeOnCompletion { launcherPeopleAvatarIconJobs.remove(entry.key) } }
+        } else {
+            val shortcut = entry.ranking.conversationShortcutInfo
+            if (shortcut != null) {
+                ic = launcherApps.getShortcutIcon(shortcut)
+            }
+        }
+
+        // Try to extract from message
         if (ic == null) {
             val extras: Bundle = entry.sbn.notification.extras
             val messages =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 5f3a83a..c05c3c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -278,8 +278,6 @@
     private OnExpandClickListener mOnExpandClickListener;
     private View.OnClickListener mOnFeedbackClickListener;
     private Path mExpandingClipPath;
-    private final RefactorFlag mInlineReplyAnimation =
-            RefactorFlag.forView(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION);
 
     private static boolean shouldSimulateSlowMeasure() {
         return Compile.IS_DEBUG && RefactorFlag.forView(
@@ -355,7 +353,7 @@
                     nowExpanded = !isExpanded();
                     setUserExpanded(nowExpanded);
                 }
-                notifyHeightChanged(true);
+                notifyHeightChanged(/* needsAnimation= */ true);
                 mOnExpandClickListener.onExpandClicked(mEntry, v, nowExpanded);
                 mMetricsLogger.action(MetricsEvent.ACTION_NOTIFICATION_EXPANDER, nowExpanded);
             }
@@ -778,7 +776,7 @@
             mChildrenContainer.updateGroupOverflow();
         }
         if (intrinsicBefore != getIntrinsicHeight()) {
-            notifyHeightChanged(false  /* needsAnimation */);
+            notifyHeightChanged(/* needsAnimation= */ false);
         }
         if (isHeadsUp) {
             mMustStayOnScreen = true;
@@ -826,7 +824,7 @@
             if (mChildrenContainer != null) {
                 mChildrenContainer.setHeaderVisibleAmount(headerVisibleAmount);
             }
-            notifyHeightChanged(false /* needsAnimation */);
+            notifyHeightChanged(/* needsAnimation= */ false);
         }
     }
 
@@ -1088,7 +1086,7 @@
         boolean wasAboveShelf = isAboveShelf();
         mIsPinned = pinned;
         if (intrinsicHeight != getIntrinsicHeight()) {
-            notifyHeightChanged(false /* needsAnimation */);
+            notifyHeightChanged(/* needsAnimation= */ false);
         }
         if (pinned) {
             setAnimationRunning(true);
@@ -2611,7 +2609,7 @@
         onExpansionChanged(true /* userAction */, wasExpanded);
         if (!wasExpanded && isExpanded()
                 && getActualHeight() != getIntrinsicHeight()) {
-            notifyHeightChanged(true /* needsAnimation */);
+            notifyHeightChanged(/* needsAnimation= */ true);
         }
     }
 
@@ -2623,7 +2621,7 @@
             if (mIsSummaryWithChildren) {
                 mChildrenContainer.onExpansionChanged();
             }
-            notifyHeightChanged(false /* needsAnimation */);
+            notifyHeightChanged(/* needsAnimation= */ false);
         }
         updateShelfIconColor();
     }
@@ -2661,7 +2659,7 @@
         if (expand != mIsSystemExpanded) {
             final boolean wasExpanded = isExpanded();
             mIsSystemExpanded = expand;
-            notifyHeightChanged(false /* needsAnimation */);
+            notifyHeightChanged(/* needsAnimation= */ false);
             onExpansionChanged(false /* userAction */, wasExpanded);
             if (mIsSummaryWithChildren) {
                 mChildrenContainer.updateGroupOverflow();
@@ -2680,7 +2678,7 @@
                 if (mIsSummaryWithChildren) {
                     mChildrenContainer.updateGroupOverflow();
                 }
-                notifyHeightChanged(false /* needsAnimation */);
+                notifyHeightChanged(/* needsAnimation= */ false);
             }
             if (isAboveShelf() != wasAboveShelf) {
                 mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
@@ -2837,7 +2835,7 @@
         super.onLayout(changed, left, top, right, bottom);
         if (intrinsicBefore != getIntrinsicHeight()
                 && (intrinsicBefore != 0 || getActualHeight() > 0)) {
-            notifyHeightChanged(true  /* needsAnimation */);
+            notifyHeightChanged(/* needsAnimation= */ true);
         }
         if (mMenuRow != null && mMenuRow.getMenuView() != null) {
             mMenuRow.onParentHeightUpdate();
@@ -2880,8 +2878,7 @@
         mSensitiveHiddenInGeneral = hideSensitive;
         int intrinsicAfter = getIntrinsicHeight();
         if (intrinsicBefore != intrinsicAfter) {
-            boolean needsAnimation = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
-            notifyHeightChanged(needsAnimation);
+            notifyHeightChanged(/* needsAnimation= */ true);
         }
     }
 
@@ -3018,7 +3015,7 @@
         if (isChildInGroup()) {
             mGroupExpansionManager.setGroupExpanded(mEntry, true);
         }
-        notifyHeightChanged(false /* needsAnimation */);
+        notifyHeightChanged(/* needsAnimation= */ false);
     }
 
     public void setChildrenExpanded(boolean expanded, boolean animate) {
@@ -3241,13 +3238,8 @@
             mGuts.setActualHeight(height);
             return;
         }
-        int contentHeight = Math.max(getMinHeight(), height);
         for (NotificationContentView l : mLayouts) {
-            if (mInlineReplyAnimation.isEnabled()) {
-                l.setContentHeight(height);
-            } else {
-                l.setContentHeight(contentHeight);
-            }
+            l.setContentHeight(height);
         }
         if (mIsSummaryWithChildren) {
             mChildrenContainer.setActualHeight(height);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 137e1b2..8a3e7e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -699,8 +699,7 @@
         int hint;
         if (mHeadsUpChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_HEADSUP)) {
             hint = getViewHeight(VISIBLE_TYPE_HEADSUP);
-            if (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isAnimatingAppearance()
-                    && mHeadsUpRemoteInputController.isFocusAnimationFlagActive()) {
+            if (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isAnimatingAppearance()) {
                 // While the RemoteInputView is animating its appearance, it should be allowed
                 // to overlap the hint, therefore no space is reserved for the hint during the
                 // appearance animation of the RemoteInputView
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 b205071..77e9425 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
@@ -87,7 +87,6 @@
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.flags.RefactorFlag;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.res.R;
@@ -197,8 +196,6 @@
      */
     private Set<Integer> mDebugTextUsedYPositions;
     private final boolean mDebugRemoveAnimation;
-    private final boolean mSensitiveRevealAnimEndabled;
-    private final RefactorFlag mAnimatedInsets;
     private int mContentHeight;
     private float mIntrinsicContentHeight;
     private int mPaddingBetweenElements;
@@ -619,9 +616,6 @@
                 Flags.LOCKSCREEN_ENABLE_LANDSCAPE);
         mDebugLines = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_LINES);
         mDebugRemoveAnimation = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION);
-        mSensitiveRevealAnimEndabled = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
-        mAnimatedInsets =
-                new RefactorFlag(mFeatureFlags, Flags.ANIMATED_NOTIFICATION_SHADE_INSETS);
         mSectionsManager = Dependency.get(NotificationSectionsManager.class);
         mScreenOffAnimationController =
                 Dependency.get(ScreenOffAnimationController.class);
@@ -656,9 +650,7 @@
         mGroupMembershipManager = Dependency.get(GroupMembershipManager.class);
         mGroupExpansionManager = Dependency.get(GroupExpansionManager.class);
         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
-        if (mAnimatedInsets.isEnabled()) {
-            setWindowInsetsAnimationCallback(mInsetsCallback);
-        }
+        setWindowInsetsAnimationCallback(mInsetsCallback);
     }
 
     /**
@@ -1734,11 +1726,7 @@
             return;
         }
         mForcedScroll = v;
-        if (mAnimatedInsets.isEnabled()) {
-            updateForcedScroll();
-        } else {
-            scrollTo(v);
-        }
+        updateForcedScroll();
     }
 
     public boolean scrollTo(View v) {
@@ -1783,31 +1771,15 @@
 
     @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-        if (!mAnimatedInsets.isEnabled()) {
-            mBottomInset = insets.getInsets(WindowInsets.Type.ime()).bottom;
-        }
         mWaterfallTopInset = 0;
         final DisplayCutout cutout = insets.getDisplayCutout();
         if (cutout != null) {
             mWaterfallTopInset = cutout.getWaterfallInsets().top;
         }
-        if (mAnimatedInsets.isEnabled() && !mIsInsetAnimationRunning) {
+        if (!mIsInsetAnimationRunning) {
             // update bottom inset e.g. after rotation
             updateBottomInset(insets);
         }
-        if (!mAnimatedInsets.isEnabled()) {
-            int range = getScrollRange();
-            if (mOwnScrollY > range) {
-                // HACK: We're repeatedly getting staggered insets here while the IME is
-                // animating away. To work around that we'll wait until things have settled.
-                removeCallbacks(mReclamp);
-                postDelayed(mReclamp, 50);
-            } else if (mForcedScroll != null) {
-                // The scroll was requested before we got the actual inset - in case we need
-                // to scroll up some more do so now.
-                scrollTo(mForcedScroll);
-            }
-        }
         return insets;
     }
 
@@ -2576,7 +2548,7 @@
             return;
         }
         child.setOnHeightChangedListener(null);
-        if (child instanceof ExpandableNotificationRow && mSensitiveRevealAnimEndabled) {
+        if (child instanceof ExpandableNotificationRow) {
             NotificationEntry entry = ((ExpandableNotificationRow) child).getEntry();
             entry.removeOnSensitivityChangedListener(mOnChildSensitivityChangedListener);
         }
@@ -2872,7 +2844,7 @@
     private void onViewAddedInternal(ExpandableView child) {
         updateHideSensitiveForChild(child);
         child.setOnHeightChangedListener(mOnChildHeightChangedListener);
-        if (child instanceof ExpandableNotificationRow && mSensitiveRevealAnimEndabled) {
+        if (child instanceof ExpandableNotificationRow) {
             NotificationEntry entry = ((ExpandableNotificationRow) child).getEntry();
             entry.addOnSensitivityChangedListener(mOnChildSensitivityChangedListener);
         }
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 2745817..5e87a7a 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
@@ -350,6 +350,9 @@
                     if (shadeExpansion > 0f || qsExpansion > 0f) {
                         if (configurationBasedDimensions.useSplitShade) {
                             emit(1f)
+                        } else if (qsExpansion == 1f) {
+                            // Ensure HUNs will be visible in QS shade (at least while unlocked)
+                            emit(1f)
                         } else {
                             // Fade as QS shade expands
                             emit(1f - qsExpansion)
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/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index d4960d7..712f65d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -328,7 +328,14 @@
     GLANCEABLE_HUB_OVER_DREAM {
         @Override
         public void prepare(ScrimState previousState) {
-            GLANCEABLE_HUB.prepare(previousState);
+            // No scrims should be visible by default in this state.
+            mBehindAlpha = 0;
+            mNotifAlpha = 0;
+            mFrontAlpha = 0;
+
+            mFrontTint = Color.TRANSPARENT;
+            mBehindTint = mBackgroundColor;
+            mNotifTint = mClipQsScrim ? mBackgroundColor : Color.TRANSPARENT;
         }
     };
 
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 8e9c038..5b14259 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -44,6 +44,7 @@
 import com.android.systemui.shade.NotificationShadeWindowView;
 import com.android.systemui.shade.QuickSettingsController;
 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.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -87,6 +88,7 @@
     private final NotificationMediaManager mMediaManager;
     private final NotificationGutsManager mGutsManager;
     private final ShadeViewController mNotificationPanel;
+    private final PanelExpansionInteractor mPanelExpansionInteractor;
     private final HeadsUpManager mHeadsUpManager;
     private final AboveShelfObserver mAboveShelfObserver;
     private final DozeScrimController mDozeScrimController;
@@ -108,6 +110,7 @@
     StatusBarNotificationPresenter(
             Context context,
             ShadeViewController panel,
+            PanelExpansionInteractor panelExpansionInteractor,
             QuickSettingsController quickSettingsController,
             HeadsUpManager headsUp,
             NotificationShadeWindowView statusBarWindow,
@@ -134,6 +137,7 @@
         mActivityStarter = activityStarter;
         mKeyguardStateController = keyguardStateController;
         mNotificationPanel = panel;
+        mPanelExpansionInteractor = panelExpansionInteractor;
         mQsController = quickSettingsController;
         mHeadsUpManager = headsUp;
         mDynamicPrivacyController = dynamicPrivacyController;
@@ -202,7 +206,7 @@
 
     @Override
     public boolean isCollapsing() {
-        return mNotificationPanel.isCollapsing()
+        return mPanelExpansionInteractor.isCollapsing()
                 || mNotificationShadeWindowController.isLaunchingActivity();
     }
 
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/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 5bced93..9633cb0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -135,7 +135,6 @@
     @Nullable
     private RevealParams mRevealParams;
     private Rect mContentBackgroundBounds;
-    private boolean mIsFocusAnimationFlagActive;
     private boolean mIsAnimatingAppearance = false;
 
     // TODO(b/193539698): move these to a Controller
@@ -432,7 +431,7 @@
         // case to prevent flicker.
         if (!mRemoved) {
             ViewGroup parent = (ViewGroup) getParent();
-            if (animate && parent != null && mIsFocusAnimationFlagActive) {
+            if (animate && parent != null) {
 
                 ViewGroup grandParent = (ViewGroup) parent.getParent();
                 View actionsContainer = getActionsContainerLayout();
@@ -497,8 +496,7 @@
     }
 
     private void setTopMargin(int topMargin) {
-        if (!(getLayoutParams() instanceof FrameLayout.LayoutParams)) return;
-        final FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
+        if (!(getLayoutParams() instanceof FrameLayout.LayoutParams layoutParams)) return;
         layoutParams.topMargin = topMargin;
         setLayoutParams(layoutParams);
     }
@@ -608,24 +606,10 @@
     }
 
     /**
-     * Sets whether the feature flag for the revised inline reply animation is active or not.
-     * @param active
-     */
-    public void setIsFocusAnimationFlagActive(boolean active) {
-        mIsFocusAnimationFlagActive = active;
-    }
-
-    /**
      * Focuses the RemoteInputView and animates its appearance
      */
     public void focusAnimated() {
-        if (!mIsFocusAnimationFlagActive && getVisibility() != VISIBLE
-                && mRevealParams != null) {
-            android.animation.Animator animator = mRevealParams.createCircularRevealAnimator(this);
-            animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-            animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
-            animator.start();
-        } else if (mIsFocusAnimationFlagActive && getVisibility() != VISIBLE) {
+        if (getVisibility() != VISIBLE) {
             mIsAnimatingAppearance = true;
             setAlpha(0f);
             Animator focusAnimator = getFocusAnimator(getActionsContainerLayout());
@@ -680,37 +664,19 @@
     }
 
     private void reset() {
-        if (mIsFocusAnimationFlagActive) {
-            mProgressBar.setVisibility(INVISIBLE);
-            mResetting = true;
-            mSending = false;
-            mController.removeSpinning(mEntry.getKey(), mToken);
-            onDefocus(true /* animate */, false /* logClose */, () -> {
-                mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText());
-                mEditText.getText().clear();
-                mEditText.setEnabled(isAggregatedVisible());
-                mSendButton.setVisibility(VISIBLE);
-                updateSendButton();
-                setAttachment(null);
-                mResetting = false;
-            });
-            return;
-        }
-
+        mProgressBar.setVisibility(INVISIBLE);
         mResetting = true;
         mSending = false;
-        mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText());
-
-        mEditText.getText().clear();
-        mEditText.setEnabled(isAggregatedVisible());
-        mSendButton.setVisibility(VISIBLE);
-        mProgressBar.setVisibility(INVISIBLE);
         mController.removeSpinning(mEntry.getKey(), mToken);
-        updateSendButton();
-        onDefocus(false /* animate */, false /* logClose */, null /* doAfterDefocus */);
-        setAttachment(null);
-
-        mResetting = false;
+        onDefocus(true /* animate */, false /* logClose */, () -> {
+            mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText());
+            mEditText.getText().clear();
+            mEditText.setEnabled(isAggregatedVisible());
+            mSendButton.setVisibility(VISIBLE);
+            updateSendButton();
+            setAttachment(null);
+            mResetting = false;
+        });
     }
 
     @Override
@@ -854,7 +820,7 @@
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
-        if (mIsFocusAnimationFlagActive) setPivotY(getMeasuredHeight());
+        setPivotY(getMeasuredHeight());
         if (mContentBackgroundBounds != null) {
             mContentBackground.setBounds(mContentBackgroundBounds);
         }
@@ -1015,9 +981,9 @@
 
         private RemoteInputView mRemoteInputView;
         boolean mShowImeOnInputConnection;
-        private LightBarController mLightBarController;
+        private final LightBarController mLightBarController;
         private InputMethodManager mInputMethodManager;
-        private ArraySet<String> mSupportedMimes = new ArraySet<>();
+        private final ArraySet<String> mSupportedMimes = new ArraySet<>();
         UserHandle mUser;
 
         public RemoteEditText(Context context, AttributeSet attrs) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
index 6c0d433..bfee9ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
@@ -32,7 +32,6 @@
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.res.R
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags.NOTIFICATION_INLINE_REPLY_ANIMATION
 import com.android.systemui.statusbar.NotificationRemoteInputManager
 import com.android.systemui.statusbar.RemoteInputController
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -64,8 +63,6 @@
 
     var revealParams: RevealParams?
 
-    val isFocusAnimationFlagActive: Boolean
-
     /**
      * Sets the smart reply that should be inserted in the remote input, or `null` if the user is
      * not editing a smart reply.
@@ -155,9 +152,6 @@
 
     override val isActive: Boolean get() = view.isActive
 
-    override val isFocusAnimationFlagActive: Boolean
-        get() = mFlags.isEnabled(NOTIFICATION_INLINE_REPLY_ANIMATION)
-
     override fun bind() {
         if (isBound) return
         isBound = true
@@ -168,7 +162,6 @@
             view.setSupportedMimeTypes(it.allowedDataTypes)
         }
         view.setRevealParameters(revealParams)
-        view.setIsFocusAnimationFlagActive(isFocusAnimationFlagActive)
 
         view.addOnEditTextFocusChangedListener(onFocusChangeListener)
         view.addOnSendRemoteInputListener(onSendRemoteInputListener)
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
index cabe831..d10554f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
@@ -32,7 +32,7 @@
 
 private const val LIMIT_BACKGROUND_DISPATCHER_THREADS = true
 
-/** Providers for various SystemIU specific coroutines-related constructs. */
+/** Providers for various SystemUI-specific coroutines-related constructs. */
 @Module
 class SysUICoroutinesModule {
     @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt
index 71df8e5..1bceee9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt
@@ -36,3 +36,7 @@
     /** Current media state is unknown yet. */
     data object Unknown : MediaDeviceSession
 }
+
+/** Returns true when the audio is playing for the [MediaDeviceSession]. */
+fun MediaDeviceSession.isPlaying(): Boolean =
+    this is MediaDeviceSession.Active && playbackState?.isActive == true
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index 37661b5..d49cb1e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor
 import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
 import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.isPlaying
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
 import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
 import javax.inject.Inject
@@ -110,9 +111,6 @@
                 null,
             )
 
-    private fun MediaDeviceSession.isPlaying(): Boolean =
-        this is MediaDeviceSession.Active && playbackState?.isActive == true
-
     fun onBarClick(expandable: Expandable) {
         actionsInteractor.onBarClick(mediaDeviceSession.value, expandable)
         volumePanelViewModel.dismissPanel()
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..a32b75a 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
@@ -115,7 +115,7 @@
             .stateIn(
                 coroutineScope,
                 SharingStarted.Eagerly,
-                SpatialAudioEnabledModel.Disabled,
+                SpatialAudioEnabledModel.Unknown,
             )
 
     /**
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 faf7434..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
@@ -21,12 +21,14 @@
 import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
 import com.android.settingslib.volume.shared.model.AudioStream
 import com.android.settingslib.volume.shared.model.AudioStreamModel
+import com.android.settingslib.volume.shared.model.RingerMode
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.res.R
 import com.android.systemui.volume.panel.component.volume.domain.interactor.VolumeSliderInteractor
 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
@@ -54,14 +56,6 @@
             AudioStream(AudioManager.STREAM_NOTIFICATION) to R.drawable.ic_volume_ringer,
             AudioStream(AudioManager.STREAM_ALARM) to R.drawable.ic_volume_alarm,
         )
-    private val mutedIconsByStream =
-        mapOf(
-            AudioStream(AudioManager.STREAM_MUSIC) to R.drawable.ic_volume_off,
-            AudioStream(AudioManager.STREAM_VOICE_CALL) to R.drawable.ic_volume_off,
-            AudioStream(AudioManager.STREAM_RING) to R.drawable.ic_volume_off,
-            AudioStream(AudioManager.STREAM_NOTIFICATION) to R.drawable.ic_volume_off,
-            AudioStream(AudioManager.STREAM_ALARM) to R.drawable.ic_volume_off,
-        )
     private val labelsByStream =
         mapOf(
             AudioStream(AudioManager.STREAM_MUSIC) to R.string.stream_music,
@@ -74,59 +68,82 @@
         mapOf(
             AudioStream(AudioManager.STREAM_NOTIFICATION) to
                 R.string.stream_notification_unavailable,
+            AudioStream(AudioManager.STREAM_ALARM) to R.string.stream_alarm_unavailable,
+            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),
-            ) { model, isEnabled ->
-                model.toState(value, isEnabled)
+                audioVolumeInteractor.ringerMode,
+            ) { model, 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())
         }
     }
 
-    private fun AudioStreamModel.toState(value: Float, isEnabled: Boolean): State {
+    override fun toggleMuted(state: SliderState) {
+        val audioViewModel = state as? State
+        audioViewModel ?: return
+        coroutineScope.launch {
+            audioVolumeInteractor.setMuted(audioStream, !audioViewModel.audioStreamModel.isMuted)
+        }
+    }
+
+    private fun AudioStreamModel.toState(
+        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(this),
+            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,
         )
     }
 
-    private fun getIcon(model: AudioStreamModel): Icon {
-        val isMutedOrNoVolume = model.isMuted || model.volume == model.minVolume
+    private fun AudioStreamModel.getIcon(ringerMode: RingerMode): Icon {
+        val isMutedOrNoVolume = isMuted || volume == minVolume
         val iconRes =
             if (isMutedOrNoVolume) {
-                mutedIconsByStream
+                when (audioStream.value) {
+                    AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_off
+                    AudioManager.STREAM_VOICE_CALL -> R.drawable.ic_volume_off
+                    AudioManager.STREAM_RING ->
+                        if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
+                            R.drawable.ic_volume_ringer_vibrate
+                        } else {
+                            R.drawable.ic_volume_off
+                        }
+                    AudioManager.STREAM_NOTIFICATION ->
+                        if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
+                            R.drawable.ic_volume_ringer_vibrate
+                        } else {
+                            R.drawable.ic_volume_off
+                        }
+                    AudioManager.STREAM_ALARM -> R.drawable.ic_volume_off
+                    else -> null
+                }
             } else {
-                iconsByStream
-            }[audioStream]
+                iconsByStream[audioStream]
+            }
                 ?: error("No icon for the stream: $audioStream")
         return Icon.Resource(iconRes, null)
     }
@@ -139,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
 
@@ -148,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/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
index 2824323..aaee24b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
@@ -18,6 +18,8 @@
 
 import android.media.AudioManager
 import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.isPlaying
 import com.android.systemui.volume.panel.component.volume.domain.interactor.CastVolumeInteractor
 import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.AudioStreamSliderViewModel
 import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.CastVolumeSliderViewModel
@@ -28,12 +30,17 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.flow.transformLatest
+import kotlinx.coroutines.launch
 
 /**
  * Controls the behaviour of the whole audio
@@ -46,6 +53,7 @@
 constructor(
     @VolumePanelScope private val scope: CoroutineScope,
     castVolumeInteractor: CastVolumeInteractor,
+    mediaOutputInteractor: MediaOutputInteractor,
     private val streamSliderViewModelFactory: AudioStreamSliderViewModel.Factory,
     private val castVolumeSliderViewModelFactory: CastVolumeSliderViewModel.Factory,
 ) {
@@ -90,4 +98,17 @@
                 remoteSessionsViewModels + streamViewModels
             }
             .stateIn(scope, SharingStarted.Eagerly, emptyList())
+
+    private val mutableIsExpanded = MutableSharedFlow<Boolean>()
+
+    val isExpanded: StateFlow<Boolean> =
+        merge(
+                mutableIsExpanded.onStart { emit(false) },
+                mediaOutputInteractor.mediaDeviceSession.map { !it.isPlaying() },
+            )
+            .stateIn(scope, SharingStarted.Eagerly, false)
+
+    fun onExpandedChanged(isExpanded: Boolean) {
+        scope.launch { mutableIsExpanded.emit(isExpanded) }
+    }
 }
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/tests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
index dd428f5..ccdcee5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
@@ -32,7 +32,6 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.animation.AnimatorTestRule;
 import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -57,7 +56,6 @@
 
     @Before
     public void setUp() throws Exception {
-        mFeatureFlags.setDefault(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION);
         mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         mDependency.injectMockDependency(NotificationMediaManager.class);
         allowTestableLooperAsMainThread();
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..f76957f 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
@@ -308,20 +308,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 +323,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();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 10b86ea..2b4e9ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -20,7 +20,6 @@
 import android.hardware.biometrics.BiometricAuthenticator
 import android.hardware.biometrics.BiometricConstants
 import android.hardware.biometrics.BiometricManager
-import android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT
 import android.hardware.biometrics.PromptInfo
 import android.hardware.biometrics.PromptVerticalListContentView
 import android.hardware.face.FaceSensorPropertiesInternal
@@ -386,7 +385,6 @@
 
     @Test
     fun testShowCredentialUI_withDescription() {
-        mSetFlagsRule.disableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
         val container = initializeFingerprintContainer(
                 authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
         )
@@ -397,6 +395,7 @@
     }
 
     @Test
+    @Ignore("b/302735104")
     fun testShowCredentialUI_withCustomBp() {
         val container = initializeFingerprintContainer(
                 authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
index 7b972d3..81d4e83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
@@ -17,9 +17,11 @@
 package com.android.systemui.biometrics.data.repository
 
 import android.hardware.biometrics.BiometricManager
+import android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT
 import android.hardware.biometrics.PromptInfo
 import android.hardware.biometrics.PromptVerticalListContentView
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.biometrics.shared.model.PromptKind
@@ -135,6 +137,8 @@
     @Test
     fun showBpWithoutIconForCredential_withCustomBp() =
         testScope.runTest {
+            mSetFlagsRule.enableFlags(Flags.FLAG_CONSTRAINT_BP)
+            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
             for (case in
                 listOf(
                     PromptKind.Biometric(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 140849b..7db4ca9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -35,6 +35,7 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.Flags.FLAG_BP_TALKBACK
+import com.android.systemui.Flags.FLAG_CONSTRAINT_BP
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.biometrics.UdfpsUtils
@@ -1256,6 +1257,7 @@
     fun descriptionOverriddenByContentView() =
         runGenericTest(contentView = promptContentView, description = "test description") {
             mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+            mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
             val contentView by collectLastValue(viewModel.contentView)
             val description by collectLastValue(viewModel.description)
 
@@ -1267,6 +1269,7 @@
     fun descriptionWithoutContentView() =
         runGenericTest(description = "test description") {
             mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+            mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
             val contentView by collectLastValue(viewModel.contentView)
             val description by collectLastValue(viewModel.description)
 
@@ -1278,6 +1281,7 @@
     fun logoIsNullIfPackageNameNotFound() =
         runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
             mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+            mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
             val logo by collectLastValue(viewModel.logo)
             assertThat(logo).isNull()
         }
@@ -1285,6 +1289,7 @@
     @Test
     fun defaultLogoIfNoLogoSet() = runGenericTest {
         mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+        mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
         val logo by collectLastValue(viewModel.logo)
         assertThat(logo).isEqualTo(defaultLogoIcon)
     }
@@ -1293,6 +1298,7 @@
     fun logoResSetByApp() =
         runGenericTest(logoRes = logoResFromApp) {
             mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+            mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
             val logo by collectLastValue(viewModel.logo)
             assertThat(logo).isEqualTo(logoFromApp)
         }
@@ -1301,6 +1307,7 @@
     fun logoBitmapSetByApp() =
         runGenericTest(logoBitmap = logoBitmapFromApp) {
             mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+            mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
             val logo by collectLastValue(viewModel.logo)
             assertThat((logo as BitmapDrawable).bitmap).isEqualTo(logoBitmapFromApp)
         }
@@ -1309,6 +1316,7 @@
     fun logoDescriptionIsEmptyIfPackageNameNotFound() =
         runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
             mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+            mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
             val logoDescription by collectLastValue(viewModel.logoDescription)
             assertThat(logoDescription).isEqualTo("")
         }
@@ -1316,6 +1324,7 @@
     @Test
     fun defaultLogoDescriptionIfNoLogoDescriptionSet() = runGenericTest {
         mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+        mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
         val logoDescription by collectLastValue(viewModel.logoDescription)
         assertThat(logoDescription).isEqualTo(defaultLogoDescription)
     }
@@ -1324,10 +1333,22 @@
     fun logoDescriptionSetByApp() =
         runGenericTest(logoDescription = logoDescriptionFromApp) {
             mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+            mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
             val logoDescription by collectLastValue(viewModel.logoDescription)
             assertThat(logoDescription).isEqualTo(logoDescriptionFromApp)
         }
 
+    @Test
+    fun iconViewLoaded() = runGenericTest {
+        val isIconViewLoaded by collectLastValue(viewModel.isIconViewLoaded)
+        // TODO(b/328677869): Add test for noIcon logic.
+        assertThat(isIconViewLoaded).isFalse()
+
+        viewModel.setIsIconViewLoaded(true)
+
+        assertThat(isIconViewLoaded).isTrue()
+    }
+
     /** Asserts that the selected buttons are visible now. */
     private suspend fun TestScope.assertButtonsVisible(
         tryAgain: Boolean = false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
index e796303..701b703 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
@@ -401,7 +401,7 @@
                 LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
                     Pair("Enter PIN", "PIN is required after lockdown"),
                 LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
-                    Pair("Enter PIN", "Update will install when device not in use"),
+                    Pair("Enter PIN", "PIN required for additional security"),
                 LockPatternUtils.StrongAuthTracker
                     .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
                     Pair(
@@ -439,7 +439,7 @@
                 LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
                     Pair("Enter PIN", "PIN is required after lockdown"),
                 LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
-                    Pair("Enter PIN", "Update will install when device not in use"),
+                    Pair("Enter PIN", "PIN required for additional security"),
                 LockPatternUtils.StrongAuthTracker
                     .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
                     Pair(
@@ -481,7 +481,7 @@
                 LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
                     Pair("Enter PIN", "PIN is required after lockdown"),
                 LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
-                    Pair("Enter PIN", "Update will install when device not in use"),
+                    Pair("Enter PIN", "PIN required for additional security"),
                 LockPatternUtils.StrongAuthTracker
                     .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
                     Pair(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
index 3f13033..45d20dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
@@ -188,6 +188,20 @@
     }
 
     @Test
+    public void testRegisterSensor_OccludingActivity() {
+        when(mKeyguardStateController.isOccluded()).thenReturn(true);
+
+        ArgumentCaptor<StatusBarStateController.StateListener> stateListenerArgumentCaptor =
+                ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
+        verify(mStatusBarStateController).addCallback(stateListenerArgumentCaptor.capture());
+
+        mFalsingCollector.onScreenTurningOn();
+        reset(mProximitySensor);
+        stateListenerArgumentCaptor.getValue().onStateChanged(StatusBarState.SHADE);
+        verify(mProximitySensor).register(any(ThresholdSensor.Listener.class));
+    }
+
+    @Test
     public void testPassThroughGesture() {
         MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
         MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
index e1e9fcb..dac88a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
@@ -532,6 +532,18 @@
             assertThat(faceAuthRepository.runningAuthRequest.value).isNull()
         }
 
+    @Test
+    fun lockedOut_providesSameValueFromRepository() =
+        testScope.runTest {
+            assertThat(underTest.lockedOut).isSameInstanceAs(faceAuthRepository.isLockedOut)
+        }
+
+    @Test
+    fun authenticated_providesSameValueFromRepository() =
+        testScope.runTest {
+            assertThat(underTest.authenticated).isSameInstanceAs(faceAuthRepository.isAuthenticated)
+        }
+
     companion object {
         private const val primaryUserId = 1
         private val primaryUser = UserInfo(primaryUserId, "test user", UserInfo.FLAG_PRIMARY)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorTest.kt
new file mode 100644
index 0000000..decbdaf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorTest.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.deviceentry.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceEntryFingerprintAuthInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val underTest = kosmos.deviceEntryFingerprintAuthInteractor
+    private val fingerprintAuthRepository = kosmos.deviceEntryFingerprintAuthRepository
+    private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
+    private val biometricSettingsRepository = kosmos.biometricSettingsRepository
+
+    @Test
+    fun isFingerprintAuthCurrentlyAllowed_allowedOnlyWhenItIsNotLockedOutAndAllowedBySettings() =
+        testScope.runTest {
+            val currentlyAllowed by collectLastValue(underTest.isFingerprintAuthCurrentlyAllowed)
+            biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
+            fingerprintAuthRepository.setLockedOut(true)
+
+            assertThat(currentlyAllowed).isFalse()
+
+            fingerprintAuthRepository.setLockedOut(false)
+            assertThat(currentlyAllowed).isTrue()
+
+            biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(false)
+            assertThat(currentlyAllowed).isFalse()
+        }
+
+    @Test
+    fun isSensorUnderDisplay_trueForUdfpsSensorTypes() =
+        testScope.runTest {
+            val isSensorUnderDisplay by collectLastValue(underTest.isSensorUnderDisplay)
+
+            fingerprintPropertyRepository.supportsUdfps()
+            assertThat(isSensorUnderDisplay).isTrue()
+
+            fingerprintPropertyRepository.supportsRearFps()
+            assertThat(isSensorUnderDisplay).isFalse()
+
+            fingerprintPropertyRepository.supportsSideFps()
+            assertThat(isSensorUnderDisplay).isFalse()
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index bcec6109..b80dcd4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -62,6 +62,7 @@
 import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth
 import kotlin.math.min
+import kotlin.test.assertEquals
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.emptyFlow
@@ -77,7 +78,6 @@
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.MockitoAnnotations
-import kotlin.test.assertEquals
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -134,7 +134,12 @@
     private lateinit var lockscreenToPrimaryBouncerTransitionViewModel:
         LockscreenToPrimaryBouncerTransitionViewModel
     @Mock
-    private lateinit var transitionInteractor: KeyguardTransitionInteractor
+    private lateinit var lockscreenToGlanceableHubTransitionViewModel:
+        LockscreenToGlanceableHubTransitionViewModel
+    @Mock
+    private lateinit var glanceableHubToLockscreenTransitionViewModel:
+        GlanceableHubToLockscreenTransitionViewModel
+    @Mock private lateinit var transitionInteractor: KeyguardTransitionInteractor
 
     private lateinit var underTest: KeyguardQuickAffordancesCombinedViewModel
 
@@ -271,6 +276,10 @@
         whenever(lockscreenToOccludedTransitionViewModel.shortcutsAlpha).thenReturn(emptyFlow())
         whenever(lockscreenToPrimaryBouncerTransitionViewModel.shortcutsAlpha)
             .thenReturn(emptyFlow())
+        whenever(lockscreenToGlanceableHubTransitionViewModel.shortcutsAlpha)
+            .thenReturn(emptyFlow())
+        whenever(glanceableHubToLockscreenTransitionViewModel.shortcutsAlpha)
+            .thenReturn(emptyFlow())
         whenever(shadeInteractor.anyExpansion).thenReturn(intendedShadeAlphaMutableStateFlow)
         whenever(transitionInteractor.finishedKeyguardState)
             .thenReturn(intendedFinishedKeyguardStateFlow)
@@ -307,6 +316,8 @@
                 offToLockscreenTransitionViewModel = offToLockscreenTransitionViewModel,
                 primaryBouncerToLockscreenTransitionViewModel =
                     primaryBouncerToLockscreenTransitionViewModel,
+                glanceableHubToLockscreenTransitionViewModel =
+                    glanceableHubToLockscreenTransitionViewModel,
                 lockscreenToAodTransitionViewModel = lockscreenToAodTransitionViewModel,
                 lockscreenToDozingTransitionViewModel = lockscreenToDozingTransitionViewModel,
                 lockscreenToDreamingHostedTransitionViewModel =
@@ -316,6 +327,8 @@
                 lockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel,
                 lockscreenToPrimaryBouncerTransitionViewModel =
                     lockscreenToPrimaryBouncerTransitionViewModel,
+                lockscreenToGlanceableHubTransitionViewModel =
+                    lockscreenToGlanceableHubTransitionViewModel,
                 transitionInteractor = transitionInteractor,
             )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 31746a2..b38d5e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -99,6 +99,7 @@
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.NotificationShadeWindowView;
 import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
 import com.android.systemui.shared.rotation.RotationButtonController;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.statusbar.CommandQueue;
@@ -580,6 +581,7 @@
                 () -> Optional.of(mCentralSurfaces),
                 mKeyguardStateController,
                 mock(ShadeViewController.class),
+                mock(PanelExpansionInteractor.class),
                 mock(NotificationRemoteInputManager.class),
                 mock(NotificationShadeDepthController.class),
                 mHandler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index 65ede89..0101741 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -51,6 +51,7 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.res.R;
+import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
 import com.android.systemui.util.animation.DisappearParameters;
 
@@ -100,6 +101,8 @@
     Configuration mConfiguration;
     @Mock
     Runnable mHorizontalLayoutListener;
+    @Mock
+    VibratorHelper mVibratorHelper;
 
     private QSPanelControllerBase<QSPanel> mController;
 
@@ -110,7 +113,8 @@
                 MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
                 DumpManager dumpManager) {
             super(view, host, qsCustomizerController, true, mediaHost, metricsLogger, uiEventLogger,
-                    qsLogger, dumpManager, new ResourcesSplitShadeStateController());
+                    qsLogger, dumpManager, new ResourcesSplitShadeStateController(),
+                    mVibratorHelper);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index 85d7d98..916e8dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
 import com.android.systemui.settings.brightness.BrightnessController
 import com.android.systemui.settings.brightness.BrightnessSliderController
+import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
 import com.android.systemui.tuner.TunerService
@@ -61,6 +62,7 @@
     @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
     @Mock private lateinit var configuration: Configuration
     @Mock private lateinit var pagedTileLayout: PagedTileLayout
+    @Mock private lateinit var vibratorHelper: VibratorHelper
 
     private val sceneContainerFlags = FakeSceneContainerFlags()
 
@@ -101,6 +103,7 @@
             statusBarKeyguardViewManager,
             ResourcesSplitShadeStateController(),
             sceneContainerFlags,
+            vibratorHelper,
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
index 2c14308..71a9a8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.qs.customize.QSCustomizerController
 import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
 import com.android.systemui.util.leak.RotationUtils
 import org.junit.After
@@ -59,6 +60,7 @@
     @Mock private lateinit var tile: QSTile
     @Mock private lateinit var tileLayout: TileLayout
     @Captor private lateinit var captor: ArgumentCaptor<QSPanel.OnConfigurationChangedListener>
+    @Mock private lateinit var vibratorHelper: VibratorHelper
 
     private val uiEventLogger = UiEventLoggerFake()
     private val dumpManager = DumpManager()
@@ -89,7 +91,8 @@
                 metricsLogger,
                 uiEventLogger,
                 qsLogger,
-                dumpManager
+                dumpManager,
+                vibratorHelper,
             )
 
         controller.init()
@@ -157,7 +160,8 @@
         metricsLogger: MetricsLogger,
         uiEventLogger: UiEventLoggerFake,
         qsLogger: QSLogger,
-        dumpManager: DumpManager
+        dumpManager: DumpManager,
+        vibratorHelper: VibratorHelper,
     ) :
         QuickQSPanelController(
             view,
@@ -170,7 +174,8 @@
             uiEventLogger,
             qsLogger,
             dumpManager,
-            ResourcesSplitShadeStateController()
+            ResourcesSplitShadeStateController(),
+            vibratorHelper,
         ) {
 
         private var rotation = RotationUtils.ROTATION_NONE
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/settings/brightness/BrightnessSliderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
index ab90b9b..25ba09a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.haptics.slider.SeekableSliderHapticPlugin
+import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.BrightnessMirrorController
 import com.android.systemui.util.mockito.any
@@ -66,6 +67,8 @@
     private lateinit var listener: ToggleSlider.Listener
     @Mock
     private lateinit var vibratorHelper: VibratorHelper
+    @Mock
+    private lateinit var activityStarter: ActivityStarter
 
     @Captor
     private lateinit var seekBarChangeCaptor: ArgumentCaptor<SeekBar.OnSeekBarChangeListener>
@@ -91,6 +94,7 @@
                 mFalsingManager,
                 uiEventLogger,
                 SeekableSliderHapticPlugin(vibratorHelper, systemClock),
+                activityStarter,
             )
         mController.init()
         mController.setOnChangedListener(listener)
@@ -120,7 +124,7 @@
     @Test
     fun testEnforceAdminRelayed() {
         mController.setEnforcedAdmin(enforcedAdmin)
-        verify(brightnessSliderView).setEnforcedAdmin(enforcedAdmin)
+        verify(brightnessSliderView).setAdminBlocker(notNull())
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 617b25d..88b239a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -481,8 +481,7 @@
         // AND status bar doesn't want it
         whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT))
             .thenReturn(false)
-        // AND shade is not fully expanded
-        whenever(notificationPanelViewController.isFullyExpanded()).thenReturn(false)
+        // AND shade is not fully expanded (mock is false by default)
         // AND the lock icon does NOT want the touch
         whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)).thenReturn(false)
         // AND quick settings controller DOES want it
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
index b548117..e90a3ac8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doNothing;
@@ -157,52 +158,52 @@
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_MEDIA_ICONS)
+    @DisableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS)
     public void inflateMediaNotificationIconsMediaEnabled_old() throws InflationException {
         finishSetupWithMediaFeatureFlagEnabled(true);
 
         mListener.onEntryInit(mMediaEntry);
         mListener.onEntryAdded(mMediaEntry);
         verify(mIconManager, never()).createIcons(eq(mMediaEntry));
-        verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+        verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
 
         mFilter.shouldFilterOut(mMediaEntry, 0);
         verify(mIconManager, times(1)).createIcons(eq(mMediaEntry));
-        verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+        verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
 
         mFilter.shouldFilterOut(mMediaEntry, 0);
         verify(mIconManager, times(1)).createIcons(eq(mMediaEntry));
-        verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry));
+        verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry),  /* usingCache = */ eq(false));
 
         mListener.onEntryRemoved(mMediaEntry, NotificationListenerService.REASON_CANCEL);
         mListener.onEntryCleanUp(mMediaEntry);
         mListener.onEntryInit(mMediaEntry);
         verify(mIconManager, times(1)).createIcons(eq(mMediaEntry));
-        verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry));
+        verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry),  /* usingCache = */ eq(false));
 
         mFilter.shouldFilterOut(mMediaEntry, 0);
         verify(mIconManager, times(2)).createIcons(eq(mMediaEntry));
-        verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry));
+        verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry), /* usingCache = */ eq(false));
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_MEDIA_ICONS)
+    @EnableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS)
     public void inflateMediaNotificationIconsMediaEnabled_new() throws InflationException {
         finishSetupWithMediaFeatureFlagEnabled(true);
 
         mListener.onEntryInit(mMediaEntry);
         mListener.onEntryAdded(mMediaEntry);
         verify(mIconManager).createIcons(eq(mMediaEntry));
-        verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+        verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
         clearInvocations(mIconManager);
 
         mFilter.shouldFilterOut(mMediaEntry, 0);
         verify(mIconManager, never()).createIcons(eq(mMediaEntry));
-        verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+        verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
 
         mListener.onEntryUpdated(mMediaEntry);
         verify(mIconManager, never()).createIcons(eq(mMediaEntry));
-        verify(mIconManager).updateIcons(eq(mMediaEntry));
+        verify(mIconManager).updateIcons(eq(mMediaEntry), /* usingCache = */ eq(false));
 
         mListener.onEntryRemoved(mMediaEntry, NotificationListenerService.REASON_CANCEL);
         mListener.onEntryCleanUp(mMediaEntry);
@@ -211,40 +212,40 @@
         mListener.onEntryInit(mMediaEntry);
         mListener.onEntryAdded(mMediaEntry);
         verify(mIconManager).createIcons(eq(mMediaEntry));
-        verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+        verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_MEDIA_ICONS)
+    @DisableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS)
     public void inflationException_old() throws InflationException {
         finishSetupWithMediaFeatureFlagEnabled(true);
 
         mListener.onEntryInit(mMediaEntry);
         mListener.onEntryAdded(mMediaEntry);
         verify(mIconManager, never()).createIcons(eq(mMediaEntry));
-        verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+        verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
 
         doThrow(InflationException.class).when(mIconManager).createIcons(eq(mMediaEntry));
         mFilter.shouldFilterOut(mMediaEntry, 0);
         verify(mIconManager, times(1)).createIcons(eq(mMediaEntry));
-        verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+        verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
 
         mFilter.shouldFilterOut(mMediaEntry, 0);
         verify(mIconManager, times(1)).createIcons(eq(mMediaEntry));
-        verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+        verify(mIconManager, never()).updateIcons(eq(mMediaEntry),  /* usingCache = */ eq(false));
 
         mListener.onEntryUpdated(mMediaEntry);
         verify(mIconManager, times(1)).createIcons(eq(mMediaEntry));
-        verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+        verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
 
         doNothing().when(mIconManager).createIcons(eq(mMediaEntry));
         mFilter.shouldFilterOut(mMediaEntry, 0);
         verify(mIconManager, times(2)).createIcons(eq(mMediaEntry));
-        verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+        verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_MEDIA_ICONS)
+    @EnableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS)
     public void inflationException_new() throws InflationException {
         finishSetupWithMediaFeatureFlagEnabled(true);
 
@@ -253,19 +254,19 @@
         mListener.onEntryInit(mMediaEntry);
         mListener.onEntryAdded(mMediaEntry);
         verify(mIconManager).createIcons(eq(mMediaEntry));
-        verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+        verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
         clearInvocations(mIconManager);
 
         mListener.onEntryUpdated(mMediaEntry);
         verify(mIconManager).createIcons(eq(mMediaEntry));
-        verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+        verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
         clearInvocations(mIconManager);
 
         doNothing().when(mIconManager).createIcons(eq(mMediaEntry));
 
         mListener.onEntryUpdated(mMediaEntry);
         verify(mIconManager).createIcons(eq(mMediaEntry));
-        verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+        verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
     }
 
     private void finishSetupWithMediaFeatureFlagEnabled(boolean mediaFeatureFlagEnabled) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
index a12806b..4ac9dc2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.statusbar.notification.icon
 
 import android.app.ActivityManager
@@ -38,6 +40,10 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -69,6 +75,11 @@
     @Mock private lateinit var notifCollection: CommonNotifCollection
     @Mock private lateinit var launcherApps: LauncherApps
 
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+    private val mainContext = testScope.coroutineContext
+    private val bgContext = testScope.backgroundScope.coroutineContext
+
     private val iconBuilder = IconBuilder(context)
 
     private lateinit var iconManager: IconManager
@@ -85,7 +96,15 @@
         `when`(shortcut.icon).thenReturn(shortcutIc)
         `when`(launcherApps.getShortcutIcon(shortcut)).thenReturn(shortcutIc)
 
-        iconManager = IconManager(notifCollection, launcherApps, iconBuilder)
+        iconManager =
+            IconManager(
+                notifCollection,
+                launcherApps,
+                iconBuilder,
+                testScope,
+                bgContext,
+                mainContext,
+            )
     }
 
     @Test
@@ -94,6 +113,7 @@
             notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = true)
         entry?.channel?.isImportantConversation = true
         entry?.let { iconManager.createIcons(it) }
+        testScope.runCurrent()
         assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(shortcutIc)
     }
 
@@ -103,6 +123,7 @@
             notificationEntry(hasShortcut = false, hasMessageSenderIcon = true, hasLargeIcon = true)
         entry?.channel?.isImportantConversation = true
         entry?.let { iconManager.createIcons(it) }
+        testScope.runCurrent()
         assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(messageIc)
     }
 
@@ -116,6 +137,7 @@
             )
         entry?.channel?.isImportantConversation = true
         entry?.let { iconManager.createIcons(it) }
+        testScope.runCurrent()
         assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(largeIc)
     }
 
@@ -129,6 +151,7 @@
             )
         entry?.channel?.isImportantConversation = true
         entry?.let { iconManager.createIcons(it) }
+        testScope.runCurrent()
         assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(smallIc)
     }
 
@@ -143,6 +166,7 @@
             )
         entry?.channel?.isImportantConversation = true
         entry?.let { iconManager.createIcons(it) }
+        testScope.runCurrent()
         assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(smallIc)
     }
 
@@ -161,6 +185,7 @@
         entry?.setSensitive(true, true)
         entry?.channel?.isImportantConversation = true
         entry?.let { iconManager.createIcons(it) }
+        testScope.runCurrent()
         assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(shortcutIc)
         assertThat(entry?.icons?.shelfIcon?.sourceIcon).isEqualTo(smallIc)
         assertThat(entry?.icons?.aodIcon?.sourceIcon).isEqualTo(smallIc)
@@ -175,6 +200,7 @@
         entry?.let { iconManager.createIcons(it) }
         // Updating the icons after creation shouldn't break anything
         entry?.let { iconManager.updateIcons(it) }
+        testScope.runCurrent()
         assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(shortcutIc)
         assertThat(entry?.icons?.shelfIcon?.sourceIcon).isEqualTo(smallIc)
         assertThat(entry?.icons?.aodIcon?.sourceIcon).isEqualTo(smallIc)
@@ -187,9 +213,11 @@
         entry?.channel?.isImportantConversation = true
         entry?.setSensitive(true, true)
         entry?.let { iconManager.createIcons(it) }
+        testScope.runCurrent()
         assertThat(entry?.icons?.aodIcon?.sourceIcon).isEqualTo(smallIc)
         entry?.setSensitive(false, false)
         entry?.let { iconManager.updateIcons(it) }
+        testScope.runCurrent()
         assertThat(entry?.icons?.shelfIcon?.sourceIcon).isEqualTo(shortcutIc)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 42a6924..b114e13 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -60,7 +60,6 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.SysuiTestableContext;
 import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
@@ -104,7 +103,6 @@
                 TestableLooper.get(this),
                 mFeatureFlags);
         mNotificationTestHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL);
-        mFeatureFlags.setDefault(Flags.SENSITIVE_REVEAL_ANIM);
     }
 
     @Test
@@ -186,14 +184,6 @@
     }
 
     @Test
-    public void testSetSensitiveOnNotifRowNotifiesOfHeightChange_withOtherFlagValue()
-            throws Exception {
-        FakeFeatureFlags flags = mFeatureFlags;
-        flags.set(Flags.SENSITIVE_REVEAL_ANIM, !flags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM));
-        testSetSensitiveOnNotifRowNotifiesOfHeightChange();
-    }
-
-    @Test
     public void testSetSensitiveOnNotifRowNotifiesOfHeightChange() throws Exception {
         // GIVEN a sensitive notification row that's currently redacted
         ExpandableNotificationRow row = mNotificationTestHelper.createRow();
@@ -210,19 +200,10 @@
         // WHEN the row is set to no longer be sensitive
         row.setSensitive(false, true);
 
-        boolean expectAnimation = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
         // VERIFY that the height change listener is invoked
         assertThat(row.getShowingLayout()).isSameInstanceAs(row.getPrivateLayout());
         assertThat(row.getIntrinsicHeight()).isGreaterThan(0);
-        verify(listener).onHeightChanged(eq(row), eq(expectAnimation));
-    }
-
-    @Test
-    public void testSetSensitiveOnGroupRowNotifiesOfHeightChange_withOtherFlagValue()
-            throws Exception {
-        FakeFeatureFlags flags = mFeatureFlags;
-        flags.set(Flags.SENSITIVE_REVEAL_ANIM, !flags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM));
-        testSetSensitiveOnGroupRowNotifiesOfHeightChange();
+        verify(listener).onHeightChanged(eq(row), eq(true));
     }
 
     @Test
@@ -242,19 +223,10 @@
         // WHEN the row is set to no longer be sensitive
         group.setSensitive(false, true);
 
-        boolean expectAnimation = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
         // VERIFY that the height change listener is invoked
         assertThat(group.getShowingLayout()).isSameInstanceAs(group.getPrivateLayout());
         assertThat(group.getIntrinsicHeight()).isGreaterThan(0);
-        verify(listener).onHeightChanged(eq(group), eq(expectAnimation));
-    }
-
-    @Test
-    public void testSetSensitiveOnPublicRowDoesNotNotifyOfHeightChange_withOtherFlagValue()
-            throws Exception {
-        FakeFeatureFlags flags = mFeatureFlags;
-        flags.set(Flags.SENSITIVE_REVEAL_ANIM, !flags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM));
-        testSetSensitiveOnPublicRowDoesNotNotifyOfHeightChange();
+        verify(listener).onHeightChanged(eq(group), eq(true));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 718f998..3b78b7e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -53,6 +53,7 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.statusbar.IStatusBarService;
+import com.android.keyguard.TestScopeProvider;
 import com.android.systemui.TestableDependency;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.flags.FakeFeatureFlags;
@@ -102,6 +103,9 @@
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
+import kotlin.coroutines.CoroutineContext;
+import kotlinx.coroutines.test.TestScope;
+
 /**
  * A helper class to create {@link ExpandableNotificationRow} (for both individual and group
  * notifications).
@@ -140,6 +144,10 @@
     private final FakeFeatureFlags mFeatureFlags;
     private final SystemClock mSystemClock;
     private final RowInflaterTaskLogger mRowInflaterTaskLogger;
+    private final TestScope mTestScope = TestScopeProvider.getTestScope();
+    private final CoroutineContext mBgCoroutineContext =
+            mTestScope.getBackgroundScope().getCoroutineContext();
+    private final CoroutineContext mMainCoroutineContext = mTestScope.getCoroutineContext();
 
     public NotificationTestHelper(
             Context context,
@@ -169,7 +177,10 @@
         mIconManager = new IconManager(
                 mock(CommonNotifCollection.class),
                 mock(LauncherApps.class),
-                new IconBuilder(mContext));
+                new IconBuilder(mContext),
+                mTestScope,
+                mBgCoroutineContext,
+                mMainCoroutineContext);
 
         NotificationContentInflater contentBinder = new NotificationContentInflater(
                 mock(NotifRemoteViewCache.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index b938029..9a7b8ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -10,7 +10,6 @@
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.res.R
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
 import com.android.systemui.statusbar.NotificationShelf
@@ -23,7 +22,6 @@
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
-import org.junit.Assume.assumeTrue
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -38,7 +36,6 @@
 @RunWithLooper
 open class NotificationShelfTest : SysuiTestCase() {
 
-    open val useSensitiveReveal: Boolean = false
     private val flags = FakeFeatureFlags()
 
     @Mock private lateinit var largeScreenShadeInterpolator: LargeScreenShadeInterpolator
@@ -53,7 +50,6 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         mDependency.injectTestDependency(FeatureFlags::class.java, flags)
-        flags.set(Flags.SENSITIVE_REVEAL_ANIM, useSensitiveReveal)
         val root = FrameLayout(context)
         shelf =
             LayoutInflater.from(root.context)
@@ -335,7 +331,6 @@
     @Test
     fun updateState_withNullLastVisibleBackgroundChild_hideShelf() {
         // GIVEN
-        assumeTrue(useSensitiveReveal)
         whenever(ambientState.stackY).thenReturn(100f)
         whenever(ambientState.stackHeight).thenReturn(100f)
         val paddingBetweenElements =
@@ -362,7 +357,6 @@
     @Test
     fun updateState_withNullFirstViewInShelf_hideShelf() {
         // GIVEN
-        assumeTrue(useSensitiveReveal)
         whenever(ambientState.stackY).thenReturn(100f)
         whenever(ambientState.stackHeight).thenReturn(100f)
         val paddingBetweenElements =
@@ -389,7 +383,6 @@
     @Test
     fun updateState_withCollapsedShade_hideShelf() {
         // GIVEN
-        assumeTrue(useSensitiveReveal)
         whenever(ambientState.stackY).thenReturn(100f)
         whenever(ambientState.stackHeight).thenReturn(100f)
         val paddingBetweenElements =
@@ -416,7 +409,6 @@
     @Test
     fun updateState_withHiddenSectionBeforeShelf_hideShelf() {
         // GIVEN
-        assumeTrue(useSensitiveReveal)
         whenever(ambientState.stackY).thenReturn(100f)
         whenever(ambientState.stackHeight).thenReturn(100f)
         val paddingBetweenElements =
@@ -476,10 +468,3 @@
         assertEquals(expectedAlpha, shelf.viewState.alpha)
     }
 }
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@RunWithLooper
-class NotificationShelfWithSensitiveRevealTest : NotificationShelfTest() {
-    override val useSensitiveReveal: Boolean = true
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 220305c..13df091 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -165,8 +165,6 @@
         // TODO: Ideally we wouldn't need to set these unless a test actually reads them,
         //  and then we would test both configurations, but currently they are all read
         //  in the constructor.
-        mFeatureFlags.setDefault(Flags.SENSITIVE_REVEAL_ANIM);
-        mFeatureFlags.setDefault(Flags.ANIMATED_NOTIFICATION_SHADE_INSETS);
         mSetFlagsRule.enableFlags(FLAG_NEW_AOD_TRANSITION);
         mFeatureFlags.setDefault(Flags.UNCLEARED_TRANSIENT_HUN_FIX);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index cdbbc93..f947640 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -747,14 +747,16 @@
 
         // Open the bouncer.
         mScrimController.setRawPanelExpansionFraction(0f);
+        when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
         mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_VISIBLE);
         finishAnimationsImmediately();
 
-        // Only behind widget is visible.
+        // Only behind scrim is visible.
         assertScrimAlpha(Map.of(
                 mScrimInFront, TRANSPARENT,
                 mNotificationsScrim, TRANSPARENT,
                 mScrimBehind, OPAQUE));
+        assertScrimTint(mScrimBehind, mSurfaceColor);
 
         // Bouncer is closed.
         mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
@@ -773,8 +775,9 @@
         mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB);
 
         // Open the shade.
-        mScrimController.transitionTo(SHADE_LOCKED);
         mScrimController.setQsPosition(1f, 0);
+        mScrimController.setRawPanelExpansionFraction(1);
+        mScrimController.setTransitionToFullShadeProgress(1, 0);
         finishAnimationsImmediately();
 
         // Shade scrims are visible.
@@ -782,8 +785,10 @@
                 mNotificationsScrim, OPAQUE,
                 mScrimInFront, TRANSPARENT,
                 mScrimBehind, OPAQUE));
+        assertScrimTint(mScrimBehind, Color.BLACK);
+        assertScrimTint(mNotificationsScrim, Color.TRANSPARENT);
 
-        mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB);
+        mScrimController.setTransitionToFullShadeProgress(0, 0);
         finishAnimationsImmediately();
 
         // All scrims are transparent.
@@ -813,14 +818,16 @@
 
         // Open the bouncer.
         mScrimController.setRawPanelExpansionFraction(0f);
+        when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
         mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_VISIBLE);
         finishAnimationsImmediately();
 
-        // Only behind widget is visible.
+        // Only behind scrim is visible.
         assertScrimAlpha(Map.of(
                 mScrimInFront, TRANSPARENT,
                 mNotificationsScrim, TRANSPARENT,
                 mScrimBehind, OPAQUE));
+        assertScrimTint(mScrimBehind, mSurfaceColor);
 
         // Bouncer is closed.
         mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
@@ -839,7 +846,6 @@
         mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
 
         // Open the shade.
-        mScrimController.transitionTo(SHADE_LOCKED);
         mScrimController.setQsPosition(1f, 0);
         mScrimController.setRawPanelExpansionFraction(1f);
         finishAnimationsImmediately();
@@ -849,8 +855,11 @@
                 mNotificationsScrim, OPAQUE,
                 mScrimInFront, TRANSPARENT,
                 mScrimBehind, OPAQUE));
+        assertScrimTint(mScrimBehind, Color.BLACK);
+        assertScrimTint(mNotificationsScrim, Color.TRANSPARENT);
 
-        mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
+        mScrimController.setQsPosition(0f, 0);
+        mScrimController.setRawPanelExpansionFraction(0f);
         finishAnimationsImmediately();
 
         // All scrims are transparent.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index b0404a0..a8c5fc3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -50,6 +50,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.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -296,6 +297,7 @@
         mStatusBarNotificationPresenter = new StatusBarNotificationPresenter(
                 mContext,
                 shadeViewController,
+                mock(PanelExpansionInteractor.class),
                 mock(QuickSettingsController.class),
                 mock(HeadsUpManager.class),
                 notificationShadeWindowView,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 13167b2..c259782 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -71,7 +71,6 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.AnimatorTestRule;
 import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -455,7 +454,6 @@
     private RemoteInputViewController bindController(
             RemoteInputView view,
             NotificationEntry entry) {
-        mFeatureFlags.set(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION, true);
         RemoteInputViewControllerImpl viewController = new RemoteInputViewControllerImpl(
                 view,
                 entry,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 974e396..5206db4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -95,6 +95,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -292,6 +293,7 @@
         assertEquals(VolumeDialogImpl.PROGRESS_HAPTICS_DISABLED, type);
     }
 
+    @Ignore("Causing breakages so ignoring to resolve, b/329099861")
     @Test
     @EnableFlags(FLAG_HAPTIC_VOLUME_SLIDER)
     public void testVolumeChange_withSliderHaptics_deliversOnProgressChangedHapticsEagerly() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt
index 6dd8d07..0660d00 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt
@@ -18,6 +18,7 @@
 import com.android.systemui.classifier.FakeClassifierModule
 import com.android.systemui.data.FakeSystemUiDataLayerModule
 import com.android.systemui.flags.FakeFeatureFlagsClassicModule
+import com.android.systemui.flags.FakeSystemPropertiesHelperModule
 import com.android.systemui.log.FakeUiEventLoggerModule
 import com.android.systemui.settings.FakeSettingsModule
 import com.android.systemui.statusbar.policy.FakeConfigurationControllerModule
@@ -33,6 +34,7 @@
             FakeConfigurationControllerModule::class,
             FakeExecutorModule::class,
             FakeFeatureFlagsClassicModule::class,
+            FakeSystemPropertiesHelperModule::class,
             FakeSettingsModule::class,
             FakeSplitShadeStateControllerModule::class,
             FakeSystemClockModule::class,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java
index 3428553..8001604 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java
@@ -30,6 +30,7 @@
  *
  * To use:
  * - locally edit your test class to inherit from MemoryTrackingTestCase instead of SysuiTestCase
+ * - Use `atest -d` to prevent files being cleaned up
  * - Watch the logcat with tag MEMORY to see the path to the .ahprof file
  * - adb pull /path/to/something.ahprof
  * - Download ahat from https://sites.google.com/corp/google.com/ahat/home
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
index 69b769e..bc0bf9d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
@@ -27,6 +27,9 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigModule
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.SystemUIDeviceEntryFaceAuthInteractor
 import com.android.systemui.scene.SceneContainerFrameworkModule
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.scene.shared.model.SceneDataSource
@@ -56,6 +59,7 @@
             CoroutineTestScopeModule::class,
             FakeSystemUiModule::class,
             SceneContainerFrameworkModule::class,
+            FaceWakeUpTriggersConfigModule::class,
         ]
 )
 interface SysUITestModule {
@@ -69,6 +73,11 @@
     @Binds @SysUISingleton fun bindsShadeInteractor(sii: ShadeInteractorImpl): ShadeInteractor
     @Binds fun bindSceneDataSource(delegator: SceneDataSourceDelegator): SceneDataSource
 
+    @Binds
+    fun provideFaceAuthInteractor(
+        sysUIFaceAuthInteractor: SystemUIDeviceEntryFaceAuthInteractor
+    ): DeviceEntryFaceAuthInteractor
+
     companion object {
         @Provides
         fun provideSysuiTestableContext(test: SysuiTestCase): SysuiTestableContext = test.context
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
index 62a1aa9..3d84291 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
@@ -17,6 +17,7 @@
 
 import android.app.ActivityManager
 import android.app.admin.DevicePolicyManager
+import android.app.trust.TrustManager
 import android.os.UserManager
 import android.service.notification.NotificationListenerService
 import android.util.DisplayMetrics
@@ -27,6 +28,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardViewController
 import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.demomode.DemoModeController
 import com.android.systemui.dump.DumpManager
@@ -36,6 +38,7 @@
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.dagger.BiometricLog
 import com.android.systemui.log.dagger.BroadcastDispatcherLog
+import com.android.systemui.log.dagger.FaceAuthLog
 import com.android.systemui.log.dagger.SceneFrameworkLog
 import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
 import com.android.systemui.model.SysUiState
@@ -65,10 +68,12 @@
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.statusbar.policy.ZenModeController
 import com.android.systemui.statusbar.window.StatusBarWindowController
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.settings.GlobalSettings
 import com.android.wm.shell.bubbles.Bubbles
 import dagger.Binds
 import dagger.Module
@@ -123,6 +128,10 @@
     @get:Provides val deviceEntryIconTransitions: Set<DeviceEntryIconTransition> = emptySet(),
     @get:Provides val communalInteractor: CommunalInteractor = mock(),
     @get:Provides val sceneLogger: SceneLogger = mock(),
+    @get:Provides val trustManager: TrustManager = mock(),
+    @get:Provides val primaryBouncerInteractor: PrimaryBouncerInteractor = mock(),
+    @get:Provides val keyguardStateController: KeyguardStateController = mock(),
+    @get:Provides val globalSettings: GlobalSettings = mock(),
 
     // log buffers
     @get:[Provides BroadcastDispatcherLog]
@@ -131,6 +140,8 @@
     val sceneLogBuffer: LogBuffer = mock(),
     @get:[Provides BiometricLog]
     val biometricLogger: LogBuffer = mock(),
+    @get:[Provides FaceAuthLog]
+    val faceAuthLogger: LogBuffer = mock(),
     @get:Provides val lsShadeTransitionLogger: LSShadeTransitionLogger = mock(),
 
     // framework mocks
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
index 68ef555..8a95136 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
@@ -19,10 +19,15 @@
 
 import android.graphics.Point
 import com.android.systemui.biometrics.shared.model.LockoutMode
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 
-class FakeFacePropertyRepository : FacePropertyRepository {
+@SysUISingleton
+class FakeFacePropertyRepository @Inject constructor() : FacePropertyRepository {
     private val faceSensorInfo = MutableStateFlow<FaceSensorInfo?>(null)
     override val sensorInfo: StateFlow<FaceSensorInfo?>
         get() = faceSensorInfo
@@ -56,3 +61,8 @@
         currentCameraInfo.value = value
     }
 }
+
+@Module
+interface FakeFacePropertyRepositoryModule {
+    @Binds fun bindFake(fake: FakeFacePropertyRepository): FacePropertyRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
index bd30fb4..60d61ac 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
@@ -68,6 +68,16 @@
         )
     }
 
+    /** setProperties as if the device supports POWER_BUTTON fingerprint sensor. */
+    fun supportsSideFps() {
+        setProperties(
+            sensorId = 0,
+            strength = SensorStrength.STRONG,
+            sensorType = FingerprintSensorType.POWER_BUTTON,
+            sensorLocations = emptyMap(),
+        )
+    }
+
     /** setProperties as if the device supports the rear fingerprint sensor. */
     fun supportsRearFps() {
         setProperties(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
index c3af437..2e2cf9a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
@@ -78,6 +78,7 @@
         val hasCredentialViewShown = kind.value !is PromptKind.Biometric
         val showBpForCredential =
             Flags.customBiometricPrompt() &&
+                com.android.systemui.Flags.constraintBp() &&
                 !Utils.isBiometricAllowed(promptInfo) &&
                 Utils.isDeviceCredentialAllowed(promptInfo) &&
                 promptInfo.contentView != null
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt
index 1c8190e..fbb8ea2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.deviceentry.data
 
 import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepositoryModule
+import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepositoryModule
 import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepositoryModule
 import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepositoryModule
 import com.android.systemui.display.data.repository.FakeDisplayRepositoryModule
@@ -35,6 +36,7 @@
             FakeDisplayRepositoryModule::class,
             FakeDisplayStateRepositoryModule::class,
             FakeFingerprintPropertyRepositoryModule::class,
+            FakeFacePropertyRepositoryModule::class,
             FakeTrustRepositoryModule::class,
         ]
 )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt
index 66c6f86..ebed922 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt
@@ -18,6 +18,7 @@
 
 package com.android.systemui.deviceentry.domain.interactor
 
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
 import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
 import com.android.systemui.kosmos.Kosmos
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -25,6 +26,8 @@
 val Kosmos.deviceEntryFingerprintAuthInteractor by
     Kosmos.Fixture {
         DeviceEntryFingerprintAuthInteractor(
+            biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
             repository = deviceEntryFingerprintAuthRepository,
+            fingerprintPropertyRepository = fingerprintPropertyRepository,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
index 0d1a31f..e73e295 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
@@ -18,8 +18,8 @@
 
 import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
-import com.android.systemui.keyguard.data.repository.deviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.data.repository.trustRepository
+import com.android.systemui.flags.fakeSystemPropertiesHelper
+import com.android.systemui.keyguard.domain.interactor.trustInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -34,9 +34,12 @@
             repository = deviceEntryRepository,
             authenticationInteractor = authenticationInteractor,
             sceneInteractor = sceneInteractor,
-            deviceEntryFaceAuthRepository = deviceEntryFaceAuthRepository,
-            trustRepository = trustRepository,
+            faceAuthInteractor = deviceEntryFaceAuthInteractor,
+            trustInteractor = trustInteractor,
             flags = sceneContainerFlags,
             deviceUnlockedInteractor = deviceUnlockedInteractor,
+            fingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
+            biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
+            systemPropertiesHelper = fakeSystemPropertiesHelper,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeSystemPropertiesHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeSystemPropertiesHelper.kt
new file mode 100644
index 0000000..2f30d34
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeSystemPropertiesHelper.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+
+@SysUISingleton
+class FakeSystemPropertiesHelper @Inject constructor() : SystemPropertiesHelper() {
+    private val fakeProperties = mutableMapOf<String, Any>()
+
+    override fun get(name: String): String {
+        return fakeProperties[name] as String
+    }
+
+    override fun get(name: String, def: String?): String {
+        return checkNotNull(fakeProperties[name] as String? ?: def)
+    }
+
+    override fun getBoolean(name: String, default: Boolean): Boolean {
+        return fakeProperties[name] as Boolean? ?: default
+    }
+
+    override fun setBoolean(name: String, value: Boolean) {
+        fakeProperties[name] = value
+    }
+
+    override fun set(name: String, value: String) {
+        fakeProperties[name] = value
+    }
+
+    override fun set(name: String, value: Int) {
+        fakeProperties[name] = value
+    }
+
+    override fun erase(name: String) {
+        fakeProperties.remove(name)
+    }
+}
+
+@Module
+interface FakeSystemPropertiesHelperModule {
+    @Binds fun bindFake(fake: FakeSystemPropertiesHelper): SystemPropertiesHelper
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
index 365d97f..d6f2f77 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
@@ -62,6 +62,7 @@
     }
 
 val Kosmos.systemPropertiesHelper by Kosmos.Fixture { SystemPropertiesHelper() }
+val Kosmos.fakeSystemPropertiesHelper by Kosmos.Fixture { FakeSystemPropertiesHelper() }
 var Kosmos.serverFlagReader: ServerFlagReader by Kosmos.Fixture { serverFlagReaderFake }
 val Kosmos.serverFlagReaderFake by Kosmos.Fixture { ServerFlagReaderFake() }
 var Kosmos.restarter: Restarter by Kosmos.Fixture { mock() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/TrustInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/TrustInteractorKosmos.kt
new file mode 100644
index 0000000..0ebf164
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/TrustInteractorKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.trustRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.trustInteractor by Fixture { TrustInteractor(repository = trustRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
index 0b13858..b34681a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
@@ -2,6 +2,7 @@
 
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 
@@ -9,3 +10,7 @@
 var Kosmos.testScope by Fixture { TestScope(testDispatcher) }
 var Kosmos.applicationCoroutineScope by Fixture { testScope.backgroundScope }
 var Kosmos.testCase: SysuiTestCase by Fixture()
+var Kosmos.backgroundCoroutineContext: CoroutineContext by Fixture {
+    testScope.backgroundScope.coroutineContext
+}
+var Kosmos.mainCoroutineContext: CoroutineContext by Fixture { testScope.coroutineContext }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorKosmos.kt
index 2a4dd3a..09c8f87 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorKosmos.kt
@@ -23,6 +23,8 @@
 val Kosmos.panelExpansionInteractor by Fixture { panelExpansionInteractorImpl }
 val Kosmos.panelExpansionInteractorImpl by Fixture {
     PanelExpansionInteractorImpl(
-        sceneInteractor = sceneInteractor,
+        sceneInteractor,
+        shadeInteractor,
+        shadeAnimationInteractor,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt
index d2dd200..6d24e2a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.shade.domain.interactor
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.shade.data.repository.shadeAnimationRepository
 
@@ -24,5 +25,9 @@
     Kosmos.Fixture { ShadeAnimationInteractorEmptyImpl(shadeAnimationRepository) }
 var Kosmos.shadeAnimationInteractorSceneContainerImpl: ShadeAnimationInteractorSceneContainerImpl by
     Kosmos.Fixture {
-        ShadeAnimationInteractorSceneContainerImpl(shadeAnimationRepository, sceneInteractor)
+        ShadeAnimationInteractorSceneContainerImpl(
+            testScope.backgroundScope,
+            shadeAnimationRepository,
+            sceneInteractor
+        )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconManagerKosmos.kt
index d3a8e0c..0950f04 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconManagerKosmos.kt
@@ -18,7 +18,19 @@
 
 import android.content.pm.launcherApps
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.backgroundCoroutineContext
+import com.android.systemui.kosmos.mainCoroutineContext
 import com.android.systemui.statusbar.notification.collection.notifcollection.commonNotifCollection
 
 val Kosmos.iconManager by
-    Kosmos.Fixture { IconManager(commonNotifCollection, launcherApps, iconBuilder) }
+    Kosmos.Fixture {
+        IconManager(
+            commonNotifCollection,
+            launcherApps,
+            iconBuilder,
+            applicationCoroutineScope,
+            backgroundCoroutineContext,
+            mainCoroutineContext,
+        )
+    }
diff --git a/packages/Tethering/OWNERS b/packages/Tethering/OWNERS
deleted file mode 100644
index aa87958..0000000
--- a/packages/Tethering/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /services/core/java/com/android/server/net/OWNERS
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index 10e6ed4..004f37c 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -59,6 +59,8 @@
 import android.hardware.camera2.extension.SizeList;
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
+import android.hardware.camera2.params.ColorSpaceProfiles;
+import android.hardware.camera2.params.DynamicRangeProfiles;
 import android.hardware.camera2.utils.SurfaceUtils;
 import android.media.Image;
 import android.media.ImageReader;
@@ -526,7 +528,7 @@
      */
     public static Pair<PreviewExtenderImpl, ImageCaptureExtenderImpl> initializeExtension(
             int extensionType) {
-        if (Flags.concertMode()) {
+        if (Flags.concertModeApi()) {
             if (extensionType == CameraExtensionCharacteristics.EXTENSION_EYES_FREE_VIDEOGRAPHY) {
                 // Basic extensions are deprecated starting with extension version 1.5
                 return new Pair<>(new PreviewExtenderImpl() {
@@ -711,7 +713,7 @@
      * @hide
      */
     public static AdvancedExtenderImpl initializeAdvancedExtensionImpl(int extensionType) {
-        if (Flags.concertMode()) {
+        if (Flags.concertModeApi()) {
             if (extensionType == CameraExtensionCharacteristics.EXTENSION_EYES_FREE_VIDEOGRAPHY) {
                 if (EFV_SUPPORTED) {
                     return new EyesFreeVideographyAdvancedExtenderImpl();
@@ -1228,7 +1230,6 @@
 
             return null;
         }
-
     }
 
     private class CaptureCallbackStub implements SessionProcessorImpl.CaptureCallback {
@@ -1585,11 +1586,13 @@
             Camera2SessionConfigImpl sessionConfig;
 
             if (LATENCY_IMPROVEMENTS_SUPPORTED) {
+                int outputsColorSpace = getColorSpaceFromOutputSurfaces(previewSurface,
+                        imageCaptureSurface, postviewSurface);
                 OutputSurfaceConfigurationImplStub outputSurfaceConfigs =
                         new OutputSurfaceConfigurationImplStub(mOutputPreviewSurfaceImpl,
                         // Image Analysis Output is currently only supported in CameraX
                         mOutputImageCaptureSurfaceImpl, null /*imageAnalysisSurfaceConfig*/,
-                        mOutputPostviewSurfaceImpl);
+                        mOutputPostviewSurfaceImpl, outputsColorSpace);
 
                 sessionConfig = mSessionProcessor.initSession(cameraId,
                         getCharacteristicsMap(charsMapNative),
@@ -1616,6 +1619,11 @@
                 }
                 ret.outputConfigs.add(entry);
             }
+            if (Flags.extension10Bit() && EFV_SUPPORTED) {
+                ret.colorSpace = sessionConfig.getColorSpace();
+            } else {
+                ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED;
+            }
             ret.sessionTemplateId = sessionConfig.getSessionTemplateId();
             ret.sessionType = -1;
             if (LATENCY_IMPROVEMENTS_SUPPORTED) {
@@ -1720,6 +1728,24 @@
         public void binderDied() {
             mSessionProcessor.deInitSession();
         }
+
+        // Get the color space of the output configurations. All of the OutputSurfaces
+        // can be assumed to have the same color space so return the color space
+        // of any non-null OutputSurface
+        private int getColorSpaceFromOutputSurfaces(OutputSurface previewSurface,
+                OutputSurface imageCaptureSurface, OutputSurface postviewSurface) {
+            int colorSpace = ColorSpaceProfiles.UNSPECIFIED;
+
+            if (previewSurface.surface != null) {
+                colorSpace = previewSurface.colorSpace;
+            } else if (imageCaptureSurface.surface != null) {
+                colorSpace = imageCaptureSurface.colorSpace;
+            } else if (postviewSurface.surface != null) {
+                colorSpace = postviewSurface.colorSpace;
+            }
+
+            return colorSpace;
+        }
     }
 
     private class OutputSurfaceConfigurationImplStub implements OutputSurfaceConfigurationImpl {
@@ -1727,6 +1753,17 @@
         private OutputSurfaceImpl mOutputImageCaptureSurfaceImpl;
         private OutputSurfaceImpl mOutputImageAnalysisSurfaceImpl;
         private OutputSurfaceImpl mOutputPostviewSurfaceImpl;
+        private int mColorSpace;
+
+        public OutputSurfaceConfigurationImplStub(OutputSurfaceImpl previewOutput,
+                OutputSurfaceImpl imageCaptureOutput, OutputSurfaceImpl imageAnalysisOutput,
+                OutputSurfaceImpl postviewOutput, int colorSpace) {
+            mOutputPreviewSurfaceImpl = previewOutput;
+            mOutputImageCaptureSurfaceImpl = imageCaptureOutput;
+            mOutputImageAnalysisSurfaceImpl = imageAnalysisOutput;
+            mOutputPostviewSurfaceImpl = postviewOutput;
+            mColorSpace = colorSpace;
+        }
 
         public OutputSurfaceConfigurationImplStub(OutputSurfaceImpl previewOutput,
                 OutputSurfaceImpl imageCaptureOutput, OutputSurfaceImpl imageAnalysisOutput,
@@ -1735,6 +1772,7 @@
             mOutputImageCaptureSurfaceImpl = imageCaptureOutput;
             mOutputImageAnalysisSurfaceImpl = imageAnalysisOutput;
             mOutputPostviewSurfaceImpl = postviewOutput;
+            mColorSpace = ColorSpaceProfiles.UNSPECIFIED;
         }
 
         @Override
@@ -1756,6 +1794,11 @@
         public OutputSurfaceImpl getPostviewOutputSurface() {
             return mOutputPostviewSurfaceImpl;
         }
+
+        @Override
+        public int getColorSpace() {
+            return mColorSpace;
+        }
     }
 
     private class OutputSurfaceImplStub implements OutputSurfaceImpl {
@@ -1764,11 +1807,10 @@
         private final int mImageFormat;
         private final int mDataspace;
         private final long mUsage;
+        private final long mDynamicRangeProfile;
 
         public OutputSurfaceImplStub(OutputSurface outputSurface) {
             mSurface = outputSurface.surface;
-            mSize = new Size(outputSurface.size.width, outputSurface.size.height);
-            mImageFormat = outputSurface.imageFormat;
             if (mSurface != null) {
                 mDataspace = SurfaceUtils.getSurfaceDataspace(mSurface);
                 mUsage = SurfaceUtils.getSurfaceUsage(mSurface);
@@ -1776,6 +1818,9 @@
                 mDataspace = -1;
                 mUsage = 0;
             }
+            mDynamicRangeProfile = outputSurface.dynamicRangeProfile;
+            mSize = new Size(outputSurface.size.width, outputSurface.size.height);
+            mImageFormat = outputSurface.imageFormat;
         }
 
         @Override
@@ -1802,6 +1847,12 @@
         public long getUsage() {
             return mUsage;
         }
+
+        @Override
+        public long getDynamicRangeProfile() {
+            return mDynamicRangeProfile;
+        }
+
     }
 
     private class PreviewExtenderImplStub extends IPreviewExtenderImpl.Stub implements
@@ -2531,6 +2582,11 @@
 
     private static CameraOutputConfig getCameraOutputConfig(Camera2OutputConfigImpl output) {
         CameraOutputConfig ret = new CameraOutputConfig();
+        if (Flags.extension10Bit() && EFV_SUPPORTED) {
+            ret.dynamicRangeProfile = output.getDynamicRangeProfile();
+        } else {
+            ret.dynamicRangeProfile = DynamicRangeProfiles.STANDARD;
+        }
         ret.outputId = new OutputConfigId();
         ret.outputId.id = output.getId();
         ret.physicalCameraId = output.getPhysicalCameraId();
diff --git a/ravenwood/README.md b/ravenwood/README.md
index 9c4fda7..8cafb43 100644
--- a/ravenwood/README.md
+++ b/ravenwood/README.md
@@ -1,9 +1,11 @@
 # Ravenwood
 
-Ravenwood is an officially-supported lightweight unit testing environment for Android platform code that runs on the host.
+Ravenwood is a lightweight unit testing environment for Android platform code that runs on the host.
 
 Ravenwood’s focus on Android platform use-cases, improved maintainability, and device consistency distinguishes it from Robolectric, which remains a popular choice for app testing.
 
+> **Note:** Active development of Ravenwood has been paused as of March 2024.  Existing Ravenwood tests will continue running, but support has moved to a self-service model.
+
 ## Background
 
 Executing tests on a typical Android device has substantial overhead, such as flashing the build, waiting for the boot to complete, and retrying tests that fail due to general flakiness.
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/accessibility/java/com/android/server/accessibility/gestures/TouchState.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
index d9e25ef..e13994e 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
@@ -525,8 +525,9 @@
             mReceivedPointersDown |= pointerFlag;
             mReceivedPointers[pointerId].set(
                     event.getX(pointerIndex), event.getY(pointerIndex), event.getEventTime());
-
-            mPrimaryPointerId = pointerId;
+            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+                mPrimaryPointerId = pointerId;
+            }
         }
 
         /**
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 1749ee3..16fe007 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -4025,8 +4025,9 @@
                 }
             }
         }
-        throw new IllegalArgumentException(
-                providerComponent + " is not a valid AppWidget provider");
+        // Either the provider does not exist or the caller does not have permission to access its
+        // previews.
+        return null;
     }
 
     @Override
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index ca2a3dd..a55b8d0 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -171,6 +171,7 @@
 import android.util.TimeUtils;
 import android.view.KeyEvent;
 import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillFeatureFlags;
 import android.view.autofill.AutofillManager;
 import android.view.autofill.AutofillManager.AutofillCommitReason;
 import android.view.autofill.AutofillManager.SmartSuggestionMode;
@@ -1498,7 +1499,7 @@
         mSessionCommittedEventLogger.maybeSetComponentPackageUid(uid);
         mSaveEventLogger = SaveEventLogger.forSessionId(sessionId);
         mIsPrimaryCredential = isPrimaryCredential;
-        mIgnoreViewStateResetToEmpty = Flags.ignoreViewStateResetToEmpty();
+        mIgnoreViewStateResetToEmpty = AutofillFeatureFlags.shouldIgnoreViewStateResetToEmpty();
 
         synchronized (mLock) {
             mSessionFlags = new SessionFlags();
diff --git a/services/backup/BACKUP_OWNERS b/services/backup/BACKUP_OWNERS
index f8f4f4f..29ae202 100644
--- a/services/backup/BACKUP_OWNERS
+++ b/services/backup/BACKUP_OWNERS
@@ -2,9 +2,10 @@
 
 jstemmer@google.com
 martinoh@google.com
-millmore@google.com
 niamhfw@google.com
 piee@google.com
 philippov@google.com
 rthakohov@google.com
-sarpm@google.com
\ No newline at end of file
+sarpm@google.com
+beatricemarch@google.com
+azilio@google.com
\ No newline at end of file
diff --git a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
index 82ab098..340bc32 100644
--- a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
+++ b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
@@ -38,7 +38,8 @@
         {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
-      ]
+      ],
+      "keywords": ["primary-device"]
     },
     {
       "name": "CtsHardwareTestCases",
diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
index 7d8aad7..ecd14ce 100644
--- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
+++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
@@ -24,6 +24,7 @@
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.media.projection.MediaProjectionInfo;
 import android.media.projection.MediaProjectionManager;
@@ -79,7 +80,7 @@
                     Trace.beginSection(
                             "SensitiveContentProtectionManagerService.onProjectionStart");
                     try {
-                        onProjectionStart(info);
+                        onProjectionStart(info.getPackageName());
                     } finally {
                         Trace.endSection();
                     }
@@ -124,14 +125,6 @@
         }
     }
 
-    // These packages are exempted from screen share protection.
-    private ArraySet<String> getExemptedPackages() {
-        final ArraySet<String> exemptedPackages =
-                SystemConfig.getInstance().getBugreportWhitelistedPackages();
-        // TODO(b/323361046) - Add sys ui recorder package.
-        return exemptedPackages;
-    }
-
     @VisibleForTesting
     void init(MediaProjectionManager projectionManager, WindowManagerInternal windowManager,
             ArraySet<String> exemptedPackages) {
@@ -179,9 +172,22 @@
         }
     }
 
-    private void onProjectionStart(MediaProjectionInfo info) {
-        if (mExemptedPackages != null && mExemptedPackages.contains(info.getPackageName())) {
-            Log.w(TAG, info.getPackageName() + " is exempted from screen share protection.");
+    private boolean canRecordSensitiveContent(@NonNull String packageName) {
+        return getContext().getPackageManager()
+                .checkPermission(android.Manifest.permission.RECORD_SENSITIVE_CONTENT,
+                        packageName) == PackageManager.PERMISSION_GRANTED;
+    }
+
+    // These packages are exempted from screen share protection.
+    private ArraySet<String> getExemptedPackages() {
+        return SystemConfig.getInstance().getBugreportWhitelistedPackages();
+    }
+
+    private void onProjectionStart(String packageName) {
+        // exempt on device screen recorder as well.
+        if ((mExemptedPackages != null && mExemptedPackages.contains(packageName))
+                || canRecordSensitiveContent(packageName)) {
+            Log.w(TAG, packageName + " is exempted from screen share protection.");
             return;
         }
         // TODO(b/324447419): move GlobalSettings lookup to background thread
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 130a733..1334a95 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -113,6 +113,7 @@
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
+import com.android.modules.expresslog.Histogram;
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
 import com.android.server.SystemService;
@@ -284,6 +285,11 @@
     private static AtomicReference<AccountManagerService> sThis = new AtomicReference<>();
     private static final Account[] EMPTY_ACCOUNT_ARRAY = new Account[]{};
 
+    private static Histogram sResponseLatency = new Histogram(
+            "app.value_high_authenticator_response_latency",
+            new Histogram.ScaledRangeOptions(20, 10000, 10000, 1.5f)
+    );
+
     /**
      * This should only be called by system code. One should only call this after the service
      * has started.
@@ -4937,6 +4943,9 @@
         protected boolean mCanStartAccountManagerActivity = false;
         protected final UserAccounts mAccounts;
 
+        private int mAuthenticatorUid;
+        private long mBindingStartTime;
+
         public Session(UserAccounts accounts, IAccountManagerResponse response, String accountType,
                 boolean expectActivityLaunch, boolean stripAuthTokenFromResult, String accountName,
                 boolean authDetailsRequired) {
@@ -4974,6 +4983,10 @@
         }
 
         IAccountManagerResponse getResponseAndClose() {
+            if (mAuthenticatorUid != 0 && mBindingStartTime > 0) {
+                sResponseLatency.logSampleWithUid(mAuthenticatorUid,
+                        SystemClock.uptimeMillis() - mBindingStartTime);
+            }
             if (mResponse == null) {
                 close();
                 return null;
@@ -5353,7 +5366,8 @@
                 mContext.unbindService(this);
                 return false;
             }
-
+            mAuthenticatorUid = authenticatorInfo.uid;
+            mBindingStartTime = SystemClock.uptimeMillis();
             return true;
         }
     }
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/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 60bfc63..447dfd9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -4755,6 +4755,7 @@
                         autofillOptions,
                         contentCaptureOptions,
                         app.getDisabledCompatChanges(),
+                        app.getLoggableCompatChanges(),
                         serializedSystemFontMap,
                         app.getStartElapsedTime(),
                         app.getStartUptime());
@@ -9010,7 +9011,7 @@
                             // cleaning up the old proxies.
                             VMRuntime.getRuntime().requestConcurrentGC();
                         }
-                    }, BackgroundThread.getHandler());
+                    }, mHandler);
             t.traceEnd(); // setBinderProxies
 
             t.traceEnd(); // ActivityManagerStartApps
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 5521381..0a6e9d3 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -286,9 +286,6 @@
     // when the flag is fused on.
     private static final int MSG_DELIVERY_TIMEOUT_SOFT = 8;
 
-    // TODO: Use the trunk stable flag.
-    private static final boolean DEFER_FROZEN_OUTGOING_BCASTS = false;
-
     private void enqueueUpdateRunningList() {
         mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);
         mLocalHandler.sendEmptyMessage(MSG_UPDATE_RUNNING_LIST);
@@ -766,7 +763,7 @@
         // TODO: Apply delivery group policies and FLAG_REPLACE_PENDING to collapse the
         // outgoing broadcasts.
         // TODO: Add traces/logs for the enqueueing outgoing broadcasts logic.
-        if (DEFER_FROZEN_OUTGOING_BCASTS && isProcessFreezable(r.callerApp)) {
+        if (Flags.deferOutgoingBcasts() && isProcessFreezable(r.callerApp)) {
             final BroadcastProcessQueue queue = getOrCreateProcessQueue(
                     r.callerApp.processName, r.callerApp.uid);
             if (queue.getOutgoingBroadcastCount() >= mConstants.MAX_FROZEN_OUTGOING_BROADCASTS) {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 27d6c60..48a9d6a 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2088,8 +2088,10 @@
                     + " with non-zero pid:" + app.getPid());
         }
         app.setDisabledCompatChanges(null);
+        app.setLoggableCompatChanges(null);
         if (mPlatformCompat != null) {
             app.setDisabledCompatChanges(mPlatformCompat.getDisabledChanges(app.info));
+            app.setLoggableCompatChanges(mPlatformCompat.getLoggableChanges(app.info));
         }
         final long startSeq = ++mProcStartSeqCounter;
         app.setStartSeq(startSeq);
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 9fa3a8b..b939089 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -259,6 +259,12 @@
     private long[] mDisabledCompatChanges;
 
     /**
+     * Set of compat changes for the process that are intended to be logged to logcat.
+     */
+    @GuardedBy("mService")
+    private long[] mLoggableCompatChanges;
+
+    /**
      * Who is watching for the death.
      */
     @GuardedBy("mService")
@@ -935,11 +941,21 @@
     }
 
     @GuardedBy("mService")
+    long[] getLoggableCompatChanges() {
+        return mLoggableCompatChanges;
+    }
+
+    @GuardedBy("mService")
     void setDisabledCompatChanges(long[] disabledCompatChanges) {
         mDisabledCompatChanges = disabledCompatChanges;
     }
 
     @GuardedBy("mService")
+    void setLoggableCompatChanges(long[] loggableCompatChanges) {
+        mLoggableCompatChanges = loggableCompatChanges;
+    }
+
+    @GuardedBy("mService")
     void unlinkDeathRecipient() {
         if (mDeathRecipient != null && mThread != null) {
             mThread.asBinder().unlinkToDeath(mDeathRecipient, 0);
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index d1bda79..7df5fdd 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -170,6 +170,7 @@
         "pixel_connectivity_gps",
         "pixel_system_sw_video",
         "pixel_watch",
+        "platform_compat",
         "platform_security",
         "pmw",
         "power",
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index e0c2425..14428c4 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -157,7 +157,7 @@
      * corresponding peers in case of BLE
      */
     void addAudioDeviceInInventoryIfNeeded(int deviceType, String address, String peerAddress,
-            @AudioDeviceCategory int category) {
+            @AudioDeviceCategory int category, boolean userDefined) {
         if (!isBluetoothOutDevice(deviceType)) {
             return;
         }
@@ -167,7 +167,11 @@
                 ads = findBtDeviceStateForAddress(peerAddress, deviceType);
             }
             if (ads != null) {
-                if (ads.getAudioDeviceCategory() != category) {
+                // if category is user defined allow to change back to unknown otherwise
+                // do not reset the category back to unknown since it might have been set
+                // before by the user
+                if (ads.getAudioDeviceCategory() != category && (userDefined
+                        || category != AUDIO_DEVICE_CATEGORY_UNKNOWN)) {
                     ads.setAudioDeviceCategory(category);
                     mDeviceBroker.postUpdatedAdiDeviceState(ads);
                     mDeviceBroker.postPersistAudioDeviceSettings();
@@ -220,9 +224,9 @@
     void addAudioDeviceWithCategoryInInventoryIfNeeded(@NonNull String address,
             @AudioDeviceCategory int btAudioDeviceCategory) {
         addAudioDeviceInInventoryIfNeeded(DEVICE_OUT_BLE_HEADSET,
-                address, "", btAudioDeviceCategory);
+                address, "", btAudioDeviceCategory, /*userDefined=*/true);
         addAudioDeviceInInventoryIfNeeded(DEVICE_OUT_BLUETOOTH_A2DP,
-                address, "", btAudioDeviceCategory);
+                address, "", btAudioDeviceCategory, /*userDefined=*/true);
 
     }
     @AudioDeviceCategory
@@ -1733,7 +1737,7 @@
                         purgeDevicesRoles_l();
                     } else {
                         addAudioDeviceInInventoryIfNeeded(device, address, "",
-                                BtHelper.getBtDeviceCategory(address));
+                                BtHelper.getBtDeviceCategory(address), /*userDefined=*/false);
                     }
                     AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                             "SCO " + (AudioSystem.isInputDevice(device) ? "source" : "sink")
@@ -2023,7 +2027,7 @@
         updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/);
 
         addAudioDeviceInInventoryIfNeeded(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, "",
-                BtHelper.getBtDeviceCategory(address));
+                BtHelper.getBtDeviceCategory(address), /*userDefined=*/false);
     }
 
     static final int[] CAPTURE_PRESETS = new int[] {AudioSource.MIC, AudioSource.CAMCORDER,
@@ -2357,7 +2361,7 @@
                 DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable");
         setCurrentAudioRouteNameIfPossible(name, false /*fromA2dp*/);
         addAudioDeviceInInventoryIfNeeded(DEVICE_OUT_HEARING_AID, address, "",
-                BtHelper.getBtDeviceCategory(address));
+                BtHelper.getBtDeviceCategory(address), /*userDefined=*/false);
         new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceAvailable")
                 .set(MediaMetrics.Property.ADDRESS, address != null ? address : "")
                 .set(MediaMetrics.Property.DEVICE,
@@ -2488,7 +2492,7 @@
             mDeviceBroker.postAccessoryPlugMediaUnmute(device);
             setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false);
             addAudioDeviceInInventoryIfNeeded(device, address, peerAddress,
-                    BtHelper.getBtDeviceCategory(address));
+                    BtHelper.getBtDeviceCategory(address), /*userDefined=*/false);
         }
 
         if (streamType == AudioSystem.STREAM_DEFAULT) {
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 48bf9f4..e915688 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -791,14 +791,12 @@
     private void registerAuthenticators() {
         BiometricHandlerProvider handlerProvider = mInjector.getBiometricHandlerProvider();
 
-        handlerProvider.getFingerprintHandler().post(() ->
-                registerFingerprintSensors(mInjector.getFingerprintAidlInstances(),
-                        mInjector.getFingerprintConfiguration(getContext()), getContext(),
-                        mInjector.getFingerprintService()));
-        handlerProvider.getFaceHandler().post(() ->
-                registerFaceSensors(mInjector.getFaceAidlInstances(),
-                        mInjector.getFaceConfiguration(getContext()), getContext(),
-                        mInjector.getFaceService()));
+        registerFingerprintSensors(mInjector.getFingerprintAidlInstances(),
+                mInjector.getFingerprintConfiguration(getContext()), getContext(),
+                mInjector.getFingerprintService(), handlerProvider);
+        registerFaceSensors(mInjector.getFaceAidlInstances(),
+                mInjector.getFaceConfiguration(getContext()), getContext(),
+                mInjector.getFaceService(), handlerProvider);
         registerIrisSensors(mInjector.getIrisConfiguration(getContext()));
     }
 
@@ -854,30 +852,38 @@
      */
     private static void registerFaceSensors(final String[] faceAidlInstances,
             final String[] hidlConfigStrings, final Context context,
-            final IFaceService faceService) {
-        final FaceSensorConfigurations mFaceSensorConfigurations =
-                new FaceSensorConfigurations(hidlConfigStrings != null
-                        && hidlConfigStrings.length > 0);
-
-        if (hidlConfigStrings != null && hidlConfigStrings.length > 0) {
-            mFaceSensorConfigurations.addHidlConfigs(hidlConfigStrings, context);
+            final IFaceService faceService, final BiometricHandlerProvider handlerProvider) {
+        if ((hidlConfigStrings == null || hidlConfigStrings.length == 0)
+                && (faceAidlInstances == null || faceAidlInstances.length == 0)) {
+            Slog.d(TAG, "No face sensors.");
+            return;
         }
 
-        if (faceAidlInstances != null && faceAidlInstances.length > 0) {
-            mFaceSensorConfigurations.addAidlConfigs(faceAidlInstances,
-                    name -> IFace.Stub.asInterface(Binder.allowBlocking(
-                            ServiceManager.waitForDeclaredService(name))));
-        }
+        handlerProvider.getFaceHandler().post(() -> {
+            final FaceSensorConfigurations mFaceSensorConfigurations =
+                    new FaceSensorConfigurations(hidlConfigStrings != null
+                            && hidlConfigStrings.length > 0);
 
-        if (faceService != null) {
-            try {
-                faceService.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "RemoteException when registering face authenticators", e);
+            if (hidlConfigStrings != null && hidlConfigStrings.length > 0) {
+                mFaceSensorConfigurations.addHidlConfigs(hidlConfigStrings, context);
             }
-        }  else if (mFaceSensorConfigurations.hasSensorConfigurations()) {
-            Slog.e(TAG, "Face configuration exists, but FaceService is null.");
-        }
+
+            if (faceAidlInstances != null && faceAidlInstances.length > 0) {
+                mFaceSensorConfigurations.addAidlConfigs(faceAidlInstances,
+                        name -> IFace.Stub.asInterface(Binder.allowBlocking(
+                                ServiceManager.waitForDeclaredService(name))));
+            }
+
+            if (faceService != null) {
+                try {
+                    faceService.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "RemoteException when registering face authenticators", e);
+                }
+            } else if (mFaceSensorConfigurations.hasSensorConfigurations()) {
+                Slog.e(TAG, "Face configuration exists, but FaceService is null.");
+            }
+        });
     }
 
     /**
@@ -885,30 +891,40 @@
      */
     private static void registerFingerprintSensors(final String[] fingerprintAidlInstances,
             final String[] hidlConfigStrings, final Context context,
-            final IFingerprintService fingerprintService) {
-        final FingerprintSensorConfigurations mFingerprintSensorConfigurations =
-                new FingerprintSensorConfigurations(!(hidlConfigStrings != null
-                        && hidlConfigStrings.length > 0));
-
-        if (hidlConfigStrings != null && hidlConfigStrings.length > 0) {
-            mFingerprintSensorConfigurations.addHidlSensors(hidlConfigStrings, context);
+            final IFingerprintService fingerprintService,
+            final BiometricHandlerProvider handlerProvider) {
+        if ((hidlConfigStrings == null || hidlConfigStrings.length == 0)
+                && (fingerprintAidlInstances == null || fingerprintAidlInstances.length == 0)) {
+            Slog.d(TAG, "No fingerprint sensors.");
+            return;
         }
 
-        if (fingerprintAidlInstances != null && fingerprintAidlInstances.length > 0) {
-            mFingerprintSensorConfigurations.addAidlSensors(fingerprintAidlInstances,
-                    name -> IFingerprint.Stub.asInterface(Binder.allowBlocking(
-                            ServiceManager.waitForDeclaredService(name))));
-        }
+        handlerProvider.getFingerprintHandler().post(() -> {
+            final FingerprintSensorConfigurations mFingerprintSensorConfigurations =
+                    new FingerprintSensorConfigurations(!(hidlConfigStrings != null
+                            && hidlConfigStrings.length > 0));
 
-        if (fingerprintService != null) {
-            try {
-                fingerprintService.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "RemoteException when registering fingerprint authenticators", e);
+            if (hidlConfigStrings != null && hidlConfigStrings.length > 0) {
+                mFingerprintSensorConfigurations.addHidlSensors(hidlConfigStrings, context);
             }
-        }  else if (mFingerprintSensorConfigurations.hasSensorConfigurations()) {
-            Slog.e(TAG, "Fingerprint configuration exists, but FingerprintService is null.");
-        }
+
+            if (fingerprintAidlInstances != null && fingerprintAidlInstances.length > 0) {
+                mFingerprintSensorConfigurations.addAidlSensors(fingerprintAidlInstances,
+                        name -> IFingerprint.Stub.asInterface(Binder.allowBlocking(
+                                ServiceManager.waitForDeclaredService(name))));
+            }
+
+            if (fingerprintService != null) {
+                try {
+                    fingerprintService.registerAuthenticatorsLegacy(
+                            mFingerprintSensorConfigurations);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "RemoteException when registering fingerprint authenticators", e);
+                }
+            } else if (mFingerprintSensorConfigurations.hasSensorConfigurations()) {
+                Slog.e(TAG, "Fingerprint configuration exists, but FingerprintService is null.");
+            }
+        });
     }
 
     /**
diff --git a/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java b/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java
index a923daa..e578861 100644
--- a/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java
+++ b/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java
@@ -16,6 +16,9 @@
 
 package com.android.server.biometrics;
 
+import static android.os.Process.THREAD_PRIORITY_DEFAULT;
+import static android.os.Process.THREAD_PRIORITY_DISPLAY;
+
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
@@ -27,9 +30,9 @@
     private static final BiometricHandlerProvider sBiometricHandlerProvider =
             new BiometricHandlerProvider();
 
-    private final Handler mBiometricsCallbackHandler;
-    private final Handler mFingerprintHandler;
-    private final Handler mFaceHandler;
+    private Handler mBiometricsCallbackHandler;
+    private Handler mFingerprintHandler;
+    private Handler mFaceHandler;
 
     /**
      * @return an instance of {@link BiometricHandlerProvider} which contains the three
@@ -39,16 +42,16 @@
         return sBiometricHandlerProvider;
     }
 
-    private BiometricHandlerProvider() {
-        mBiometricsCallbackHandler = getNewHandler("BiometricsCallbackHandler");
-        mFingerprintHandler = getNewHandler("FingerprintHandler");
-        mFaceHandler = getNewHandler("FaceHandler");
-    }
+    private BiometricHandlerProvider() {}
 
     /**
     * @return the handler to process all biometric callback operations
     */
     public synchronized Handler getBiometricCallbackHandler() {
+        if (mBiometricsCallbackHandler == null) {
+            mBiometricsCallbackHandler = getNewHandler("BiometricsCallbackHandler",
+                    THREAD_PRIORITY_DISPLAY);
+        }
         return mBiometricsCallbackHandler;
     }
 
@@ -56,6 +59,9 @@
      * @return the handler to process all face related biometric operations
      */
     public synchronized Handler getFaceHandler() {
+        if (mFaceHandler == null) {
+            mFaceHandler = getNewHandler("FaceHandler", THREAD_PRIORITY_DEFAULT);
+        }
         return mFaceHandler;
     }
 
@@ -63,12 +69,15 @@
      * @return the handler to process all fingerprint related biometric operations
      */
     public synchronized Handler getFingerprintHandler() {
+        if (mFingerprintHandler == null) {
+            mFingerprintHandler = getNewHandler("FingerprintHandler", THREAD_PRIORITY_DEFAULT);
+        }
         return mFingerprintHandler;
     }
 
-    private Handler getNewHandler(String tag) {
+    private Handler getNewHandler(String tag, int priority) {
         if (Flags.deHidl()) {
-            HandlerThread handlerThread = new HandlerThread(tag);
+            HandlerThread handlerThread = new HandlerThread(tag, priority);
             handlerThread.start();
             return new Handler(handlerThread.getLooper());
         }
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
index 03acf72..d93ff9d 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
@@ -82,13 +82,6 @@
                     Slogf.w(TAG, "No module %s with id %d (HAL AIDL)", name, moduleId);
                     return;
                 }
-                try {
-                    radioModule.setInternalHalCallback();
-                } catch (RemoteException ex) {
-                    Slogf.wtf(TAG, ex, "Broadcast radio module %s with id %d (HAL AIDL) "
-                            + "cannot register HAL callback", name, moduleId);
-                    return;
-                }
                 if (DEBUG) {
                     Slogf.d(TAG, "Loaded broadcast radio module %s with id %d (HAL AIDL)",
                             name, moduleId);
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
index 4b3444d..cd86510 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
@@ -246,10 +246,6 @@
         return mProperties;
     }
 
-    void setInternalHalCallback() throws RemoteException {
-        mService.setTunerCallback(mHalTunerCallback);
-    }
-
     TunerSession openSession(android.hardware.radio.ITunerCallback userCb)
             throws RemoteException {
         mLogger.logRadioEvent("Open TunerSession");
@@ -257,10 +253,14 @@
         Boolean antennaConnected;
         RadioManager.ProgramInfo currentProgramInfo;
         synchronized (mLock) {
+            boolean isFirstTunerSession = mAidlTunerSessions.isEmpty();
             tunerSession = new TunerSession(this, mService, userCb);
             mAidlTunerSessions.add(tunerSession);
             antennaConnected = mAntennaConnected;
             currentProgramInfo = mCurrentProgramInfo;
+            if (isFirstTunerSession) {
+                mService.setTunerCallback(mHalTunerCallback);
+            }
         }
         // Propagate state to new client.
         // Note: These callbacks are invoked while holding mLock to prevent race conditions
@@ -284,7 +284,6 @@
         synchronized (mLock) {
             tunerSessions = new TunerSession[mAidlTunerSessions.size()];
             mAidlTunerSessions.toArray(tunerSessions);
-            mAidlTunerSessions.clear();
         }
 
         for (TunerSession tunerSession : tunerSessions) {
@@ -402,6 +401,14 @@
             mAidlTunerSessions.remove(tunerSession);
         }
         onTunerSessionProgramListFilterChanged(null);
+        if (mAidlTunerSessions.isEmpty()) {
+            try {
+                mService.unsetTunerCallback();
+            } catch (RemoteException ex) {
+                Slogf.wtf(TAG, ex, "Failed to unregister HAL callback for module %d",
+                        mProperties.getId());
+            }
+        }
     }
 
     // add to mHandler queue
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index 9102cfd..79025d0 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -25,6 +25,7 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.os.Build;
 import android.os.Environment;
 import android.text.TextUtils;
 import android.util.LongArray;
@@ -72,7 +73,6 @@
  * been configured.
  */
 final class CompatConfig {
-
     private static final String TAG = "CompatConfig";
     private static final String APP_COMPAT_DATA_DIR = "/data/misc/appcompat";
     private static final String STATIC_OVERRIDES_PRODUCT_DIR = "/product/etc/appcompat";
@@ -149,6 +149,56 @@
     }
 
     /**
+     * Retrieves the set of changes that are intended to be logged. This includes changes that
+     * target the most recent SDK version and are not disabled.
+     *
+     * @param app the app in question
+     * @return a sorted long array of change IDs
+     */
+    long[] getLoggableChanges(ApplicationInfo app) {
+        LongArray loggable = new LongArray(mChanges.size());
+        for (CompatChange c : mChanges.values()) {
+            long changeId = c.getId();
+            boolean isLatestSdk = isChangeTargetingLatestSdk(c, app.targetSdkVersion);
+            if (c.isEnabled(app, mAndroidBuildClassifier) && isLatestSdk) {
+                loggable.add(changeId);
+            }
+        }
+        final long[] sortedChanges = loggable.toArray();
+        Arrays.sort(sortedChanges);
+        return sortedChanges;
+    }
+
+    /**
+     * Whether the change indicated by the given changeId is targeting the latest SDK version.
+     * @param c             the change for which to check the target SDK version
+     * @param appSdkVersion the target sdk version of the app
+     * @return true if the changeId targets the current sdk version or the current development
+     * version.
+     */
+    boolean isChangeTargetingLatestSdk(CompatChange c, int appSdkVersion) {
+        int maxTargetSdk = maxTargetSdkForCompatChange(c) + 1;
+        if (maxTargetSdk <= 0) {
+            // No max target sdk found.
+            return false;
+        }
+
+        return maxTargetSdk == Build.VERSION_CODES.CUR_DEVELOPMENT || maxTargetSdk == appSdkVersion;
+    }
+
+    /**
+     * Retrieves the CompatChange associated with the given changeId. Will return null if the
+     * changeId is not found. Used only for performance improvement purposes, in order to reduce
+     * lookups.
+     *
+     * @param changeId for which to look up the CompatChange
+     * @return the found compat change, or null if not found.
+     */
+    CompatChange getCompatChange(long changeId) {
+        return mChanges.get(changeId);
+    }
+
+    /**
      * Looks up a change ID by name.
      *
      * @param name name of the change to look up
@@ -164,7 +214,7 @@
     }
 
     /**
-     * Checks if a given change is enabled for a given application.
+     * Checks if a given change id is enabled for a given application.
      *
      * @param changeId the ID of the change in question
      * @param app      app to check for
@@ -173,6 +223,18 @@
      */
     boolean isChangeEnabled(long changeId, ApplicationInfo app) {
         CompatChange c = mChanges.get(changeId);
+        return isChangeEnabled(c, app);
+    }
+
+    /**
+     * Checks if a given change is enabled for a given application.
+     *
+     * @param c   the CompatChange in question
+     * @param app the app to check for
+     * @return {@code true} if the change is enabled for this app. Also returns {@code true} if the
+     * change ID is not known, as unknown changes are enabled by default.
+     */
+    boolean isChangeEnabled(CompatChange c, ApplicationInfo app) {
         if (c == null) {
             // we know nothing about this change: default behaviour is enabled.
             return true;
@@ -301,9 +363,21 @@
     /**
      * Returns the maximum SDK version for which this change can be opted in (or -1 if it is not
      * target SDK gated).
+     *
+     * @param changeId the id of the CompatChange to check for the max target sdk
      */
     int maxTargetSdkForChangeIdOptIn(long changeId) {
         CompatChange c = mChanges.get(changeId);
+        return maxTargetSdkForCompatChange(c);
+    }
+
+    /**
+     * Returns the maximum SDK version for which this change can be opted in (or -1 if it is not
+     * target SDK gated).
+     *
+     * @param c the CompatChange to check for the max target sdk
+     */
+    int maxTargetSdkForCompatChange(CompatChange c) {
         if (c != null && c.getEnableSinceTargetSdk() != -1) {
             return c.getEnableSinceTargetSdk() - 1;
         }
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 6cca130..f8fd0a0 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -120,8 +120,16 @@
         reportChangeInternal(changeId, uid, ChangeReporter.STATE_LOGGED);
     }
 
+    /**
+     * Report the change, but skip over the sdk target version check. This can be used to force the
+     * debug logs.
+     *
+     * @param changeId        of the change to report
+     * @param uid             of the user
+     * @param state           of the change - enabled/disabled/logged
+     */
     private void reportChangeInternal(long changeId, int uid, int state) {
-        mChangeReporter.reportChange(uid, changeId, state);
+        mChangeReporter.reportChange(uid, changeId, state, true);
     }
 
     @Override
@@ -164,15 +172,25 @@
     }
 
     /**
-     * Internal version of {@link #isChangeEnabled(long, ApplicationInfo)}.
+     * Internal version of {@link #isChangeEnabled(long, ApplicationInfo)}. If the provided appInfo
+     * is not null, also reports the change.
+     *
+     * @param changeId of the change to report
+     * @param appInfo  the app to check
      *
      * <p>Does not perform costly permission check.
      */
     public boolean isChangeEnabledInternal(long changeId, ApplicationInfo appInfo) {
-        boolean enabled = isChangeEnabledInternalNoLogging(changeId, appInfo);
+        // Fetch the CompatChange. This is done here instead of in mCompatConfig to avoid multiple
+        // fetches.
+        CompatChange c = mCompatConfig.getCompatChange(changeId);
+
+        boolean enabled = mCompatConfig.isChangeEnabled(c, appInfo);
+        int state = enabled ? ChangeReporter.STATE_ENABLED : ChangeReporter.STATE_DISABLED;
         if (appInfo != null) {
-            reportChangeInternal(changeId, appInfo.uid,
-                    enabled ? ChangeReporter.STATE_ENABLED : ChangeReporter.STATE_DISABLED);
+            boolean isTargetingLatestSdk =
+                    mCompatConfig.isChangeTargetingLatestSdk(c, appInfo.targetSdkVersion);
+            mChangeReporter.reportChange(appInfo.uid, changeId, state, isTargetingLatestSdk);
         }
         return enabled;
     }
@@ -399,6 +417,19 @@
     }
 
     /**
+     * Retrieves the set of changes that should be logged for a given app. Any change ID not in the
+     * returned array is ignored for logging purposes.
+     *
+     * @param appInfo The app in question
+     * @return A sorted long array of change IDs. We use a primitive array to minimize memory
+     * footprint: Every app process will store this array statically so we aim to reduce
+     * overhead as much as possible.
+     */
+    public long[] getLoggableChanges(ApplicationInfo appInfo) {
+        return mCompatConfig.getLoggableChanges(appInfo);
+    }
+
+    /**
      * Look up a change ID by name.
      *
      * @param name Name of the change to look up
diff --git a/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java b/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java
new file mode 100644
index 0000000..133c79f
--- /dev/null
+++ b/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java
@@ -0,0 +1,129 @@
+/*
+ * 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.crashrecovery;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
+import android.net.ConnectivityModuleConnector;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.server.PackageWatchdog;
+import com.android.server.pm.ApexManager;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Provides helper methods for the CrashRecovery APEX
+ *
+ * @hide
+ */
+public final class CrashRecoveryHelper {
+    private static final String TAG = "CrashRecoveryHelper";
+
+    private final ApexManager mApexManager;
+    private final Context mContext;
+    private final ConnectivityModuleConnector mConnectivityModuleConnector;
+
+
+    /** @hide */
+    public CrashRecoveryHelper(@NonNull Context context) {
+        mContext = context;
+        mApexManager = ApexManager.getInstance();
+        mConnectivityModuleConnector = ConnectivityModuleConnector.getInstance();
+    }
+
+    /**
+     * Returns true if the package name is the name of a module.
+     * If the package is an APK inside an APEX then it will use the parent's APEX package name
+     * do determine if it is a module or not.
+     * @hide
+     */
+    @AnyThread
+    public boolean isModule(@NonNull String packageName) {
+        String apexPackageName =
+                mApexManager.getActiveApexPackageNameContainingPackage(packageName);
+        if (apexPackageName != null) {
+            packageName = apexPackageName;
+        }
+
+        PackageManager pm = mContext.getPackageManager();
+        try {
+            return pm.getModuleInfo(packageName, 0) != null;
+        } catch (PackageManager.NameNotFoundException ignore) {
+            return false;
+        }
+    }
+
+    /**
+     * Register health listeners for explicit package failures.
+     * Currently only registering for Connectivity Module health.
+     * @hide
+     */
+    public void registerConnectivityModuleHealthListener(@NonNull int failureReason) {
+        // register listener for ConnectivityModule
+        mConnectivityModuleConnector.registerHealthListener(
+                packageName -> {
+                final VersionedPackage pkg = getVersionedPackage(packageName);
+                if (pkg == null) {
+                    Slog.wtf(TAG, "NetworkStack failed but could not find its package");
+                    return;
+                }
+                final List<VersionedPackage> pkgList = Collections.singletonList(pkg);
+                PackageWatchdog.getInstance(mContext).onPackageFailure(pkgList, failureReason);
+            });
+    }
+
+    @Nullable
+    private VersionedPackage getVersionedPackage(String packageName) {
+        final PackageManager pm = mContext.getPackageManager();
+        if (pm == null || TextUtils.isEmpty(packageName)) {
+            return null;
+        }
+        try {
+            final long versionCode = getPackageInfo(packageName).getLongVersionCode();
+            return new VersionedPackage(packageName, versionCode);
+        } catch (PackageManager.NameNotFoundException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Gets PackageInfo for the given package. Matches any user and apex.
+     *
+     * @throws PackageManager.NameNotFoundException if no such package is installed.
+     */
+    private PackageInfo getPackageInfo(String packageName)
+            throws PackageManager.NameNotFoundException {
+        PackageManager pm = mContext.getPackageManager();
+        try {
+            // The MATCH_ANY_USER flag doesn't mix well with the MATCH_APEX
+            // flag, so make two separate attempts to get the package info.
+            // We don't need both flags at the same time because we assume
+            // apex files are always installed for all users.
+            return pm.getPackageInfo(packageName, PackageManager.MATCH_ANY_USER);
+        } catch (PackageManager.NameNotFoundException e) {
+            return pm.getPackageInfo(packageName, PackageManager.MATCH_APEX);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/crashrecovery/OWNERS b/services/core/java/com/android/server/crashrecovery/OWNERS
new file mode 100644
index 0000000..daa0211
--- /dev/null
+++ b/services/core/java/com/android/server/crashrecovery/OWNERS
@@ -0,0 +1,3 @@
+ancr@google.com
+harshitmahajan@google.com
+robertogil@google.com
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/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 3b05b47..a7748f4 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -44,6 +44,15 @@
  * </p>
  */
 abstract class DisplayDevice {
+    /**
+     * Maximum acceptable anisotropy for the output image.
+     *
+     * Necessary to avoid unnecessary scaling when pixels are almost square, as they are non ideal
+     * anyway. For external displays, we expect an anisotropy of about 2% even if the pixels
+     * are, in fact, square due to the imprecision of the display's actual size (parsed from edid
+     * and rounded to the nearest cm).
+     */
+    static final float MAX_ANISOTROPY = 1.025f;
     private static final String TAG = "DisplayDevice";
     private static final Display.Mode EMPTY_DISPLAY_MODE = new Display.Mode.Builder().build();
 
@@ -69,13 +78,21 @@
     // Do not use for any other purpose.
     DisplayDeviceInfo mDebugLastLoggedDeviceInfo;
 
-    public DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken, String uniqueId,
+    private final boolean mIsAnisotropyCorrectionEnabled;
+
+    DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken, String uniqueId,
             Context context) {
+        this(displayAdapter, displayToken, uniqueId, context, false);
+    }
+
+    DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken, String uniqueId,
+            Context context, boolean isAnisotropyCorrectionEnabled) {
         mDisplayAdapter = displayAdapter;
         mDisplayToken = displayToken;
         mUniqueId = uniqueId;
         mDisplayDeviceConfig = null;
         mContext = context;
+        mIsAnisotropyCorrectionEnabled = isAnisotropyCorrectionEnabled;
     }
 
     /**
@@ -143,8 +160,17 @@
         DisplayDeviceInfo displayDeviceInfo = getDisplayDeviceInfoLocked();
         final boolean isRotated = mCurrentOrientation == ROTATION_90
                 || mCurrentOrientation == ROTATION_270;
-        return isRotated ? new Point(displayDeviceInfo.height, displayDeviceInfo.width)
-                : new Point(displayDeviceInfo.width, displayDeviceInfo.height);
+        var width = displayDeviceInfo.width;
+        var height = displayDeviceInfo.height;
+        if (mIsAnisotropyCorrectionEnabled && displayDeviceInfo.yDpi > 0
+                    && displayDeviceInfo.xDpi > 0) {
+            if (displayDeviceInfo.xDpi > displayDeviceInfo.yDpi * MAX_ANISOTROPY) {
+                height = (int) (height * displayDeviceInfo.xDpi / displayDeviceInfo.yDpi + 0.5);
+            } else if (displayDeviceInfo.xDpi * MAX_ANISOTROPY < displayDeviceInfo.yDpi) {
+                width = (int) (width * displayDeviceInfo.yDpi / displayDeviceInfo.xDpi  + 0.5);
+            }
+        }
+        return isRotated ? new Point(height, width) : new Point(width, height);
     }
 
     /**
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/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 88c24e0..b2fd9ed 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -257,7 +257,8 @@
                 SurfaceControl.DynamicDisplayInfo dynamicInfo,
                 SurfaceControl.DesiredDisplayModeSpecs modeSpecs, boolean isFirstDisplay) {
             super(LocalDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + physicalDisplayId,
-                    getContext());
+                    getContext(),
+                    getFeatureFlags().isPixelAnisotropyCorrectionInLogicalDisplayEnabled());
             mPhysicalDisplayId = physicalDisplayId;
             mIsFirstDisplay = isFirstDisplay;
             updateDisplayPropertiesLocked(staticDisplayInfo, dynamicInfo, modeSpecs);
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index db636d6..5eaaf35 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -35,7 +35,6 @@
 
 import com.android.server.display.layout.Layout;
 import com.android.server.display.mode.DisplayModeDirector;
-import com.android.server.wm.utils.DisplayInfoOverrides;
 import com.android.server.wm.utils.InsetUtils;
 
 import java.io.PrintWriter;
@@ -204,7 +203,28 @@
     private SparseArray<SurfaceControl.RefreshRateRange> mThermalRefreshRateThrottling =
             new SparseArray<>();
 
+    /**
+     * If the aspect ratio of the resolution of the display does not match the physical aspect
+     * ratio of the display, then without this feature enabled, picture would appear stretched to
+     * the user. This is because applications assume that they are rendered on square pixels
+     * (meaning density of pixels in x and y directions are equal). This would result into circles
+     * appearing as ellipses to the user.
+     * To compensate for non-square (anisotropic) pixels, if this feature is enabled:
+     * 1. LogicalDisplay will add more pixels for the applications to render on, as if the pixels
+     * were square and occupied the full display.
+     * 2. SurfaceFlinger will squeeze this taller/wider surface into the available number of
+     * physical pixels in the current display resolution.
+     * 3. If a setting on the display itself is set to "fill the entire display panel" then the
+     * display will stretch the pixels to fill the display fully.
+     */
+    private final boolean mIsAnisotropyCorrectionEnabled;
+
     LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) {
+        this(displayId, layerStack, primaryDisplayDevice, false);
+    }
+
+    LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice,
+            boolean isAnisotropyCorrectionEnabled) {
         mDisplayId = displayId;
         mLayerStack = layerStack;
         mPrimaryDisplayDevice = primaryDisplayDevice;
@@ -215,6 +235,7 @@
         mThermalBrightnessThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID;
         mPowerThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID;
         mBaseDisplayInfo.thermalBrightnessThrottlingDataId = mThermalBrightnessThrottlingDataId;
+        mIsAnisotropyCorrectionEnabled = isAnisotropyCorrectionEnabled;
     }
 
     public void setDevicePositionLocked(int position) {
@@ -453,6 +474,14 @@
             int maskedWidth = deviceInfo.width - maskingInsets.left - maskingInsets.right;
             int maskedHeight = deviceInfo.height - maskingInsets.top - maskingInsets.bottom;
 
+            if (mIsAnisotropyCorrectionEnabled && deviceInfo.xDpi > 0 && deviceInfo.yDpi > 0) {
+                if (deviceInfo.xDpi > deviceInfo.yDpi * DisplayDevice.MAX_ANISOTROPY) {
+                    maskedHeight = (int) (maskedHeight * deviceInfo.xDpi / deviceInfo.yDpi + 0.5);
+                } else if (deviceInfo.xDpi * DisplayDevice.MAX_ANISOTROPY < deviceInfo.yDpi) {
+                    maskedWidth = (int) (maskedWidth * deviceInfo.yDpi / deviceInfo.xDpi + 0.5);
+                }
+            }
+
             mBaseDisplayInfo.type = deviceInfo.type;
             mBaseDisplayInfo.address = deviceInfo.address;
             mBaseDisplayInfo.deviceProductInfo = deviceInfo.deviceProductInfo;
@@ -666,6 +695,31 @@
         physWidth -= maskingInsets.left + maskingInsets.right;
         physHeight -= maskingInsets.top + maskingInsets.bottom;
 
+        var displayLogicalWidth = displayInfo.logicalWidth;
+        var displayLogicalHeight = displayInfo.logicalHeight;
+
+        if (mIsAnisotropyCorrectionEnabled && displayDeviceInfo.xDpi > 0
+                    && displayDeviceInfo.yDpi > 0) {
+            if (displayDeviceInfo.xDpi > displayDeviceInfo.yDpi * DisplayDevice.MAX_ANISOTROPY) {
+                var scalingFactor = displayDeviceInfo.yDpi / displayDeviceInfo.xDpi;
+                if (rotated) {
+                    displayLogicalWidth = (int) ((float) displayLogicalWidth * scalingFactor + 0.5);
+                } else {
+                    displayLogicalHeight = (int) ((float) displayLogicalHeight * scalingFactor
+                                                          + 0.5);
+                }
+            } else if (displayDeviceInfo.xDpi * DisplayDevice.MAX_ANISOTROPY
+                               < displayDeviceInfo.yDpi) {
+                var scalingFactor = displayDeviceInfo.xDpi / displayDeviceInfo.yDpi;
+                if (rotated) {
+                    displayLogicalHeight = (int) ((float) displayLogicalHeight * scalingFactor
+                                                          + 0.5);
+                } else {
+                    displayLogicalWidth = (int) ((float) displayLogicalWidth * scalingFactor + 0.5);
+                }
+            }
+        }
+
         // Determine whether the width or height is more constrained to be scaled.
         //    physWidth / displayInfo.logicalWidth    => letter box
         // or physHeight / displayInfo.logicalHeight  => pillar box
@@ -675,16 +729,16 @@
         // comparing them.
         int displayRectWidth, displayRectHeight;
         if ((displayInfo.flags & Display.FLAG_SCALING_DISABLED) != 0 || mDisplayScalingDisabled) {
-            displayRectWidth = displayInfo.logicalWidth;
-            displayRectHeight = displayInfo.logicalHeight;
-        } else if (physWidth * displayInfo.logicalHeight
-                < physHeight * displayInfo.logicalWidth) {
+            displayRectWidth = displayLogicalWidth;
+            displayRectHeight = displayLogicalHeight;
+        } else if (physWidth * displayLogicalHeight
+                < physHeight * displayLogicalWidth) {
             // Letter box.
             displayRectWidth = physWidth;
-            displayRectHeight = displayInfo.logicalHeight * physWidth / displayInfo.logicalWidth;
+            displayRectHeight = displayLogicalHeight * physWidth / displayLogicalWidth;
         } else {
             // Pillar box.
-            displayRectWidth = displayInfo.logicalWidth * physHeight / displayInfo.logicalHeight;
+            displayRectWidth = displayLogicalWidth * physHeight / displayLogicalHeight;
             displayRectHeight = physHeight;
         }
         int displayRectTop = (physHeight - displayRectHeight) / 2;
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 3452e0f..e092fda 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -1151,7 +1151,8 @@
      */
     private LogicalDisplay createNewLogicalDisplayLocked(DisplayDevice device, int displayId) {
         final int layerStack = assignLayerStackLocked(displayId);
-        final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device);
+        final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device,
+                mFlags.isPixelAnisotropyCorrectionInLogicalDisplayEnabled());
         display.updateLocked(mDisplayDeviceRepo);
 
         final DisplayInfo info = display.getDisplayInfoLocked();
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 3c98ee4..15ee937 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -121,6 +121,11 @@
             Flags::refreshRateVotingTelemetry
     );
 
+    private final FlagState mPixelAnisotropyCorrectionEnabled = new FlagState(
+            Flags.FLAG_ENABLE_PIXEL_ANISOTROPY_CORRECTION,
+            Flags::enablePixelAnisotropyCorrection
+    );
+
     private final FlagState mSensorBasedBrightnessThrottling = new FlagState(
             Flags.FLAG_SENSOR_BASED_BRIGHTNESS_THROTTLING,
             Flags::sensorBasedBrightnessThrottling
@@ -259,6 +264,10 @@
         return mRefreshRateVotingTelemetry.isEnabled();
     }
 
+    public boolean isPixelAnisotropyCorrectionInLogicalDisplayEnabled() {
+        return mPixelAnisotropyCorrectionEnabled.isEnabled();
+    }
+
     public boolean isSensorBasedBrightnessThrottlingEnabled() {
         return mSensorBasedBrightnessThrottling.isEnabled();
     }
@@ -290,6 +299,7 @@
         pw.println(" " + mAutoBrightnessModesFlagState);
         pw.println(" " + mFastHdrTransitions);
         pw.println(" " + mRefreshRateVotingTelemetry);
+        pw.println(" " + mPixelAnisotropyCorrectionEnabled);
         pw.println(" " + mSensorBasedBrightnessThrottling);
         pw.println(" " + mRefactorDisplayPowerController);
     }
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 3404527..9bf36e4 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -186,6 +186,14 @@
 }
 
 flag {
+    name: "enable_pixel_anisotropy_correction"
+    namespace: "display_manager"
+    description: "Feature flag for enabling display anisotropy correction through LogicalDisplay upscaling"
+    bug: "317363416"
+    is_fixed_read_only: true
+}
+
+flag {
     name: "sensor_based_brightness_throttling"
     namespace: "display_manager"
     description: "Feature flag for enabling brightness throttling using sensor from config."
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 8b4e1ff..64cbd54 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -1015,12 +1015,15 @@
                 // Infinity means that we want the highest possible refresh rate
                 minRefreshRate = highestRefreshRate;
 
-                if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled) {
-                    // The flag had been turned off, we need to restore the original value
+                if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled
+                        && displayId == Display.DEFAULT_DISPLAY) {
+                    // The flag has been turned off, we need to restore the original value. We'll
+                    // use the peak refresh rate of the default display.
                     Settings.System.putFloatForUser(cr, Settings.System.MIN_REFRESH_RATE,
                             highestRefreshRate, cr.getUserId());
                 }
             } else if (mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled
+                    && displayId == Display.DEFAULT_DISPLAY
                     && Math.round(minRefreshRate) == Math.round(highestRefreshRate)) {
                 // The flag has been turned on, we need to upgrade the setting
                 Settings.System.putFloatForUser(cr, Settings.System.MIN_REFRESH_RATE,
@@ -1033,12 +1036,15 @@
                 // Infinity means that we want the highest possible refresh rate
                 peakRefreshRate = highestRefreshRate;
 
-                if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled) {
-                    // The flag had been turned off, we need to restore the original value
+                if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled
+                        && displayId == Display.DEFAULT_DISPLAY) {
+                    // The flag has been turned off, we need to restore the original value. We'll
+                    // use the peak refresh rate of the default display.
                     Settings.System.putFloatForUser(cr, Settings.System.PEAK_REFRESH_RATE,
                             highestRefreshRate, cr.getUserId());
                 }
             } else if (mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled
+                    && displayId == Display.DEFAULT_DISPLAY
                     && Math.round(peakRefreshRate) == Math.round(highestRefreshRate)) {
                 // The flag has been turned on, we need to upgrade the setting
                 Settings.System.putFloatForUser(cr, Settings.System.PEAK_REFRESH_RATE,
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index 0ef23e9..3fafca8 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -61,7 +61,6 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
 
 /**
@@ -133,7 +132,11 @@
 
         @Override
         public int getSystemGrammaticalGender(AttributionSource attributionSource, int userId) {
-            return canGetSystemGrammaticalGender(attributionSource)
+            if (!checkSystemGrammaticalGenderPermission(mPermissionManager, attributionSource)) {
+                throw new SecurityException("AttributionSource: " + attributionSource
+                        + " does not have READ_SYSTEM_GRAMMATICAL_GENDER permission.");
+            }
+            return checkSystemTermsOfAddressIsEnabled()
                     ? GrammaticalInflectionService.this.getSystemGrammaticalGender(
                     attributionSource, userId)
                     : GRAMMATICAL_GENDER_NOT_SPECIFIED;
@@ -263,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();
@@ -277,7 +284,6 @@
         } catch (RemoteException e) {
             Log.w(TAG, "Can not update configuration", e);
         }
-        Trace.endSection();
     }
 
     public int getSystemGrammaticalGender(AttributionSource attributionSource, int userId) {
@@ -369,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 996477d..a0e910d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -67,7 +67,6 @@
 import android.annotation.Nullable;
 import android.annotation.UiThread;
 import android.annotation.UserIdInt;
-import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -196,21 +195,16 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 import java.security.InvalidParameterException;
-import java.time.Instant;
-import java.time.ZoneId;
-import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
-import java.util.Locale;
 import java.util.Objects;
 import java.util.OptionalInt;
 import java.util.WeakHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
 import java.util.function.IntConsumer;
 
@@ -290,6 +284,10 @@
     final Context mContext;
     final Resources mRes;
     private final Handler mHandler;
+
+    /**
+     * TODO(b/329163064): Remove this field.
+     */
     @NonNull
     @MultiUserUnawareField
     private InputMethodSettings mSettings;
@@ -730,344 +728,9 @@
     private final CopyOnWriteArrayList<InputMethodListListener> mInputMethodListListeners =
             new CopyOnWriteArrayList<>();
 
-    /**
-     * Internal state snapshot when
-     * {@link IInputMethod#startInput(IInputMethod.StartInputParams)} is about to be called.
-     *
-     * <p>Calling that IPC endpoint basically means that
-     * {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called
-     * back in the current IME process shortly, which will also affect what the current IME starts
-     * receiving from {@link InputMethodService#getCurrentInputConnection()}. In other words, this
-     * snapshot will be taken every time when {@link InputMethodManagerService} is initiating a new
-     * logical input session between the client application and the current IME.</p>
-     *
-     * <p>Be careful to not keep strong references to this object forever, which can prevent
-     * {@link StartInputInfo#mImeToken} and {@link StartInputInfo#mTargetWindow} from being GC-ed.
-     * </p>
-     */
-    private static class StartInputInfo {
-        private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
-
-        final int mSequenceNumber;
-        final long mTimestamp;
-        final long mWallTime;
-        @UserIdInt
-        final int mImeUserId;
-        @NonNull
-        final IBinder mImeToken;
-        final int mImeDisplayId;
-        @NonNull
-        final String mImeId;
-        @StartInputReason
-        final int mStartInputReason;
-        final boolean mRestarting;
-        @UserIdInt
-        final int mTargetUserId;
-        final int mTargetDisplayId;
-        @Nullable
-        final IBinder mTargetWindow;
-        @NonNull
-        final EditorInfo mEditorInfo;
-        @SoftInputModeFlags
-        final int mTargetWindowSoftInputMode;
-        final int mClientBindSequenceNumber;
-
-        StartInputInfo(@UserIdInt int imeUserId, @NonNull IBinder imeToken, int imeDisplayId,
-                @NonNull String imeId, @StartInputReason int startInputReason, boolean restarting,
-                @UserIdInt int targetUserId, int targetDisplayId, @Nullable IBinder targetWindow,
-                @NonNull EditorInfo editorInfo, @SoftInputModeFlags int targetWindowSoftInputMode,
-                int clientBindSequenceNumber) {
-            mSequenceNumber = sSequenceNumber.getAndIncrement();
-            mTimestamp = SystemClock.uptimeMillis();
-            mWallTime = System.currentTimeMillis();
-            mImeUserId = imeUserId;
-            mImeToken = imeToken;
-            mImeDisplayId = imeDisplayId;
-            mImeId = imeId;
-            mStartInputReason = startInputReason;
-            mRestarting = restarting;
-            mTargetUserId = targetUserId;
-            mTargetDisplayId = targetDisplayId;
-            mTargetWindow = targetWindow;
-            mEditorInfo = editorInfo;
-            mTargetWindowSoftInputMode = targetWindowSoftInputMode;
-            mClientBindSequenceNumber = clientBindSequenceNumber;
-        }
-    }
-
     @GuardedBy("ImfLock.class")
     private final WeakHashMap<IBinder, IBinder> mImeTargetWindowMap = new WeakHashMap<>();
 
-    @VisibleForTesting
-    static final class SoftInputShowHideHistory {
-        private final Entry[] mEntries = new Entry[16];
-        private int mNextIndex = 0;
-        private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
-
-        static final class Entry {
-            final int mSequenceNumber = sSequenceNumber.getAndIncrement();
-            @Nullable
-            final ClientState mClientState;
-            @SoftInputModeFlags
-            final int mFocusedWindowSoftInputMode;
-            @SoftInputShowHideReason
-            final int mReason;
-            // The timing of handling showCurrentInputLocked() or hideCurrentInputLocked().
-            final long mTimestamp;
-            final long mWallTime;
-            final boolean mInFullscreenMode;
-            @NonNull
-            final String mFocusedWindowName;
-            @Nullable
-            final EditorInfo mEditorInfo;
-            @NonNull
-            final String mRequestWindowName;
-            @Nullable
-            final String mImeControlTargetName;
-            @Nullable
-            final String mImeTargetNameFromWm;
-            @Nullable
-            final String mImeSurfaceParentName;
-
-            Entry(ClientState client, EditorInfo editorInfo,
-                    String focusedWindowName, @SoftInputModeFlags int softInputMode,
-                    @SoftInputShowHideReason int reason,
-                    boolean inFullscreenMode, String requestWindowName,
-                    @Nullable String imeControlTargetName, @Nullable String imeTargetName,
-                    @Nullable String imeSurfaceParentName) {
-                mClientState = client;
-                mEditorInfo = editorInfo;
-                mFocusedWindowName = focusedWindowName;
-                mFocusedWindowSoftInputMode = softInputMode;
-                mReason = reason;
-                mTimestamp = SystemClock.uptimeMillis();
-                mWallTime = System.currentTimeMillis();
-                mInFullscreenMode = inFullscreenMode;
-                mRequestWindowName = requestWindowName;
-                mImeControlTargetName = imeControlTargetName;
-                mImeTargetNameFromWm = imeTargetName;
-                mImeSurfaceParentName = imeSurfaceParentName;
-            }
-        }
-
-        void addEntry(@NonNull Entry entry) {
-            final int index = mNextIndex;
-            mEntries[index] = entry;
-            mNextIndex = (mNextIndex + 1) % mEntries.length;
-        }
-
-        void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
-            final DateTimeFormatter formatter =
-                    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
-                            .withZone(ZoneId.systemDefault());
-
-            for (int i = 0; i < mEntries.length; ++i) {
-                final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
-                if (entry == null) {
-                    continue;
-                }
-                pw.print(prefix);
-                pw.println("SoftInputShowHide #" + entry.mSequenceNumber + ":");
-
-                pw.print(prefix);
-                pw.println("  time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
-                        + " (timestamp=" + entry.mTimestamp + ")");
-
-                pw.print(prefix);
-                pw.print("  reason=" + InputMethodDebug.softInputDisplayReasonToString(
-                        entry.mReason));
-                pw.println(" inFullscreenMode=" + entry.mInFullscreenMode);
-
-                pw.print(prefix);
-                pw.println("  requestClient=" + entry.mClientState);
-
-                pw.print(prefix);
-                pw.println("  focusedWindowName=" + entry.mFocusedWindowName);
-
-                pw.print(prefix);
-                pw.println("  requestWindowName=" + entry.mRequestWindowName);
-
-                pw.print(prefix);
-                pw.println("  imeControlTargetName=" + entry.mImeControlTargetName);
-
-                pw.print(prefix);
-                pw.println("  imeTargetNameFromWm=" + entry.mImeTargetNameFromWm);
-
-                pw.print(prefix);
-                pw.println("  imeSurfaceParentName=" + entry.mImeSurfaceParentName);
-
-                pw.print(prefix);
-                pw.print("  editorInfo:");
-                if (entry.mEditorInfo != null) {
-                    pw.print(" inputType=" + entry.mEditorInfo.inputType);
-                    pw.print(" privateImeOptions=" + entry.mEditorInfo.privateImeOptions);
-                    pw.println(" fieldId (viewId)=" + entry.mEditorInfo.fieldId);
-                } else {
-                    pw.println(" null");
-                }
-
-                pw.print(prefix);
-                pw.println("  focusedWindowSoftInputMode=" + InputMethodDebug.softInputModeToString(
-                        entry.mFocusedWindowSoftInputMode));
-            }
-        }
-    }
-
-    /**
-     * A ring buffer to store the history of {@link StartInputInfo}.
-     */
-    private static final class StartInputHistory {
-        /**
-         * Entry size for non low-RAM devices.
-         *
-         * <p>TODO: Consider to follow what other system services have been doing to manage
-         * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
-         */
-        private static final int ENTRY_SIZE_FOR_HIGH_RAM_DEVICE = 32;
-
-        /**
-         * Entry size for low-RAM devices.
-         *
-         * <p>TODO: Consider to follow what other system services have been doing to manage
-         * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
-         */
-        private static final int ENTRY_SIZE_FOR_LOW_RAM_DEVICE = 5;
-
-        private static int getEntrySize() {
-            if (ActivityManager.isLowRamDeviceStatic()) {
-                return ENTRY_SIZE_FOR_LOW_RAM_DEVICE;
-            } else {
-                return ENTRY_SIZE_FOR_HIGH_RAM_DEVICE;
-            }
-        }
-
-        /**
-         * Backing store for the ring buffer.
-         */
-        private final Entry[] mEntries = new Entry[getEntrySize()];
-
-        /**
-         * An index of {@link #mEntries}, to which next {@link #addEntry(StartInputInfo)} should
-         * write.
-         */
-        private int mNextIndex = 0;
-
-        /**
-         * Recyclable entry to store the information in {@link StartInputInfo}.
-         */
-        private static final class Entry {
-            int mSequenceNumber;
-            long mTimestamp;
-            long mWallTime;
-            @UserIdInt
-            int mImeUserId;
-            @NonNull
-            String mImeTokenString;
-            int mImeDisplayId;
-            @NonNull
-            String mImeId;
-            @StartInputReason
-            int mStartInputReason;
-            boolean mRestarting;
-            @UserIdInt
-            int mTargetUserId;
-            int mTargetDisplayId;
-            @NonNull
-            String mTargetWindowString;
-            @NonNull
-            EditorInfo mEditorInfo;
-            @SoftInputModeFlags
-            int mTargetWindowSoftInputMode;
-            int mClientBindSequenceNumber;
-
-            Entry(@NonNull StartInputInfo original) {
-                set(original);
-            }
-
-            void set(@NonNull StartInputInfo original) {
-                mSequenceNumber = original.mSequenceNumber;
-                mTimestamp = original.mTimestamp;
-                mWallTime = original.mWallTime;
-                mImeUserId = original.mImeUserId;
-                // Intentionally convert to String so as not to keep a strong reference to a Binder
-                // object.
-                mImeTokenString = String.valueOf(original.mImeToken);
-                mImeDisplayId = original.mImeDisplayId;
-                mImeId = original.mImeId;
-                mStartInputReason = original.mStartInputReason;
-                mRestarting = original.mRestarting;
-                mTargetUserId = original.mTargetUserId;
-                mTargetDisplayId = original.mTargetDisplayId;
-                // Intentionally convert to String so as not to keep a strong reference to a Binder
-                // object.
-                mTargetWindowString = String.valueOf(original.mTargetWindow);
-                mEditorInfo = original.mEditorInfo;
-                mTargetWindowSoftInputMode = original.mTargetWindowSoftInputMode;
-                mClientBindSequenceNumber = original.mClientBindSequenceNumber;
-            }
-        }
-
-        /**
-         * Add a new entry and discard the oldest entry as needed.
-         * @param info {@link StartInputInfo} to be added.
-         */
-        void addEntry(@NonNull StartInputInfo info) {
-            final int index = mNextIndex;
-            if (mEntries[index] == null) {
-                mEntries[index] = new Entry(info);
-            } else {
-                mEntries[index].set(info);
-            }
-            mNextIndex = (mNextIndex + 1) % mEntries.length;
-        }
-
-        void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
-            final DateTimeFormatter formatter =
-                    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
-                            .withZone(ZoneId.systemDefault());
-
-            for (int i = 0; i < mEntries.length; ++i) {
-                final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
-                if (entry == null) {
-                    continue;
-                }
-                pw.print(prefix);
-                pw.println("StartInput #" + entry.mSequenceNumber + ":");
-
-                pw.print(prefix);
-                pw.println("  time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
-                        + " (timestamp=" + entry.mTimestamp + ")"
-                        + " reason="
-                        + InputMethodDebug.startInputReasonToString(entry.mStartInputReason)
-                        + " restarting=" + entry.mRestarting);
-
-                pw.print(prefix);
-                pw.print("  imeToken=" + entry.mImeTokenString + " [" + entry.mImeId + "]");
-                pw.print(" imeUserId=" + entry.mImeUserId);
-                pw.println(" imeDisplayId=" + entry.mImeDisplayId);
-
-                pw.print(prefix);
-                pw.println("  targetWin=" + entry.mTargetWindowString
-                        + " [" + entry.mEditorInfo.packageName + "]"
-                        + " targetUserId=" + entry.mTargetUserId
-                        + " targetDisplayId=" + entry.mTargetDisplayId
-                        + " clientBindSeq=" + entry.mClientBindSequenceNumber);
-
-                pw.print(prefix);
-                pw.println("  softInputMode=" + InputMethodDebug.softInputModeToString(
-                        entry.mTargetWindowSoftInputMode));
-
-                pw.print(prefix);
-                pw.println("  inputType=0x" + Integer.toHexString(entry.mEditorInfo.inputType)
-                        + " imeOptions=0x" + Integer.toHexString(entry.mEditorInfo.imeOptions)
-                        + " fieldId=0x" + Integer.toHexString(entry.mEditorInfo.fieldId)
-                        + " fieldName=" + entry.mEditorInfo.fieldName
-                        + " actionId=" + entry.mEditorInfo.actionId
-                        + " actionLabel=" + entry.mEditorInfo.actionLabel);
-            }
-        }
-    }
-
     @GuardedBy("ImfLock.class")
     @NonNull
     private final StartInputHistory mStartInputHistory = new StartInputHistory();
@@ -1208,7 +871,18 @@
             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);
+                if (userId == mSettings.getUserId()) {
+                    mSettings = settings;
+                }
+            }
+            postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */);
             // If the locale is changed, needs to reset the default ime
             resetDefaultImeLocked(mContext);
             updateFromSettingsLocked(true);
@@ -1404,13 +1078,7 @@
                 final boolean isCurrentUser = (userId == mSettings.getUserId());
                 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();
@@ -1454,13 +1122,20 @@
                     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;
+                }
+                mSettings = queryInputMethodServicesInternal(mContext, userId,
+                        newAdditionalSubtypeMap, DirectBootAwareness.AUTO);
+                postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
 
                 boolean changed = false;
 
@@ -1612,17 +1287,20 @@
 
     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="
+                        + mSettings.getUserId());
             }
-            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 (mSettings.getUserId() == userId) {
+                mSettings = newSettings;
                 // We need to rebuild IMEs.
-                buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
+                postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
                 updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
             }
         }
@@ -1688,12 +1366,13 @@
 
         mShowOngoingImeSwitcherForPhones = false;
 
-        AdditionalSubtypeMapRepository.initialize(mHandler);
+        // InputMethodSettingsRepository should be initialized before buildInputMethodListLocked
+        InputMethodSettingsRepository.initialize(mHandler, mContext);
+        AdditionalSubtypeMapRepository.initialize(mHandler, mContext);
 
         final int userId = mActivityManagerInternal.getCurrentUserId();
 
-        // mSettings should be created before buildInputMethodListLocked
-        mSettings = InputMethodSettings.createEmptyMap(userId);
+        mSettings = InputMethodSettingsRepository.get(userId);
 
         mSwitchingController =
                 InputMethodSubtypeSwitchingController.createInstanceLocked(context,
@@ -1856,7 +1535,12 @@
         // The mSystemReady flag is set during boot phase,
         // and user switch would not happen at that time.
         resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_USER);
-        buildInputMethodListLocked(initialUserSwitch);
+
+        final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+                newUserId, AdditionalSubtypeMapRepository.get(newUserId), DirectBootAwareness.AUTO);
+        InputMethodSettingsRepository.put(newUserId, newSettings);
+        mSettings = newSettings;
+        postInputMethodSettingUpdatedLocked(initialUserSwitch /* resetDefaultEnabledIme */);
         if (TextUtils.isEmpty(mSettings.getSelectedInputMethod())) {
             // This is the first time of the user switch and
             // set the current ime to the proper one.
@@ -1937,7 +1621,13 @@
 
                 final String defaultImiId = mSettings.getSelectedInputMethod();
                 final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
-                buildInputMethodListLocked(!imeSelectedOnBoot /* resetDefaultEnabledIme */);
+                final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+                        currentUserId, AdditionalSubtypeMapRepository.get(currentUserId),
+                        DirectBootAwareness.AUTO);
+                InputMethodSettingsRepository.put(currentUserId, newSettings);
+                mSettings = newSettings;
+                postInputMethodSettingUpdatedLocked(
+                        !imeSelectedOnBoot /* resetDefaultEnabledIme */);
                 updateFromSettingsLocked(true);
                 InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
                         getPackageManagerForUser(mContext, currentUserId),
@@ -2044,9 +1734,7 @@
                         && (!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()
@@ -2070,9 +1758,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);
@@ -2096,7 +1783,7 @@
             methodList = mSettings.getEnabledInputMethodList();
             settings = mSettings;
         } else {
-            settings = queryMethodMapForUserLocked(userId);
+            settings = InputMethodSettingsRepository.get(userId);
             methodList = settings.getEnabledInputMethodList();
         }
         // filter caller's access to input methods
@@ -2156,22 +1843,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();
@@ -4337,8 +4009,7 @@
                 return mSettings.getLastInputMethodSubtype();
             }
 
-            final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
-            return settings.getLastInputMethodSubtype();
+            return InputMethodSettingsRepository.get(userId).getLastInputMethodSubtype();
         }
     }
 
@@ -4370,19 +4041,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 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 */);
+                        mSettings = newSettings;
+                        postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
                     } finally {
                         Binder.restoreCallingIdentity(ident);
                     }
@@ -4412,8 +4085,7 @@
         try {
             synchronized (ImfLock.class) {
                 final boolean currentUser = (mSettings.getUserId() == userId);
-                final InputMethodSettings settings = currentUser
-                        ? mSettings : queryMethodMapForUserLocked(userId);
+                final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
                 if (!settings.setEnabledInputMethodSubtypes(imeId, subtypeHashCodes)) {
                     return;
                 }
@@ -5310,7 +4982,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));
@@ -5322,10 +4994,6 @@
         mMethodMapUpdateCount++;
         mMyPackageMonitor.clearKnownImePackageNamesLocked();
 
-        mSettings = queryInputMethodServicesInternal(mContext, mSettings.getUserId(),
-                AdditionalSubtypeMapRepository.get(mSettings.getUserId()),
-                DirectBootAwareness.AUTO);
-
         // 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
         // changed.
@@ -5596,8 +5264,8 @@
                 return getCurrentInputMethodSubtypeLocked();
             }
 
-            final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
-            return settings.getCurrentInputMethodSubtypeForNonCurrentUsers();
+            return InputMethodSettingsRepository.get(userId)
+                    .getCurrentInputMethodSubtypeForNonCurrentUsers();
         }
     }
 
@@ -5659,27 +5327,11 @@
      */
     @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)
@@ -5690,7 +5342,7 @@
             setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID);
             return true;
         }
-        final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
         if (!settings.getMethodMap().containsKey(imeId)
                 || !settings.getEnabledInputMethodList().contains(
                         settings.getMethodMap().get(imeId))) {
@@ -5830,7 +5482,7 @@
                     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.
                 }
@@ -6584,7 +6236,7 @@
                 previouslyEnabled = setInputMethodEnabledLocked(imeId, enabled);
             }
         } else {
-            final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+            final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
             if (enabled) {
                 if (!settings.getMethodMap().containsKey(imeId)) {
                     failedToEnableUnknownIme = true;
@@ -6718,10 +6370,8 @@
                         nextIme = mSettings.getSelectedInputMethod();
                         nextEnabledImes = mSettings.getEnabledInputMethodList();
                     } else {
-                        final AdditionalSubtypeMap additionalSubtypeMap =
-                                AdditionalSubtypeMapRepository.get(userId);
-                        final InputMethodSettings settings = queryInputMethodServicesInternal(
-                                mContext, userId, additionalSubtypeMap, DirectBootAwareness.AUTO);
+                        final InputMethodSettings settings =
+                                InputMethodSettingsRepository.get(userId);
 
                         nextEnabledImes = InputMethodInfoUtils.getDefaultEnabledImes(mContext,
                                 settings.getMethodList());
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/inputmethod/SoftInputShowHideHistory.java b/services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java
new file mode 100644
index 0000000..3023603
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java
@@ -0,0 +1,149 @@
+/*
+ * 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.Nullable;
+import android.os.SystemClock;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.internal.inputmethod.InputMethodDebug;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
+
+import java.io.PrintWriter;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+import java.util.concurrent.atomic.AtomicInteger;
+
+final class SoftInputShowHideHistory {
+    private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
+
+    private final Entry[] mEntries = new Entry[16];
+    private int mNextIndex = 0;
+
+    static final class Entry {
+        final int mSequenceNumber = sSequenceNumber.getAndIncrement();
+        @Nullable
+        final ClientState mClientState;
+        @WindowManager.LayoutParams.SoftInputModeFlags
+        final int mFocusedWindowSoftInputMode;
+        @SoftInputShowHideReason
+        final int mReason;
+        // The timing of handling showCurrentInputLocked() or hideCurrentInputLocked().
+        final long mTimestamp;
+        final long mWallTime;
+        final boolean mInFullscreenMode;
+        @NonNull
+        final String mFocusedWindowName;
+        @Nullable
+        final EditorInfo mEditorInfo;
+        @NonNull
+        final String mRequestWindowName;
+        @Nullable
+        final String mImeControlTargetName;
+        @Nullable
+        final String mImeTargetNameFromWm;
+        @Nullable
+        final String mImeSurfaceParentName;
+
+        Entry(ClientState client, EditorInfo editorInfo,
+                String focusedWindowName,
+                @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode,
+                @SoftInputShowHideReason int reason,
+                boolean inFullscreenMode, String requestWindowName,
+                @Nullable String imeControlTargetName, @Nullable String imeTargetName,
+                @Nullable String imeSurfaceParentName) {
+            mClientState = client;
+            mEditorInfo = editorInfo;
+            mFocusedWindowName = focusedWindowName;
+            mFocusedWindowSoftInputMode = softInputMode;
+            mReason = reason;
+            mTimestamp = SystemClock.uptimeMillis();
+            mWallTime = System.currentTimeMillis();
+            mInFullscreenMode = inFullscreenMode;
+            mRequestWindowName = requestWindowName;
+            mImeControlTargetName = imeControlTargetName;
+            mImeTargetNameFromWm = imeTargetName;
+            mImeSurfaceParentName = imeSurfaceParentName;
+        }
+    }
+
+    void addEntry(@NonNull Entry entry) {
+        final int index = mNextIndex;
+        mEntries[index] = entry;
+        mNextIndex = (mNextIndex + 1) % mEntries.length;
+    }
+
+    void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+        final DateTimeFormatter formatter =
+                DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
+                        .withZone(ZoneId.systemDefault());
+
+        for (int i = 0; i < mEntries.length; ++i) {
+            final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
+            if (entry == null) {
+                continue;
+            }
+            pw.print(prefix);
+            pw.println("SoftInputShowHide #" + entry.mSequenceNumber + ":");
+
+            pw.print(prefix);
+            pw.println("  time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
+                    + " (timestamp=" + entry.mTimestamp + ")");
+
+            pw.print(prefix);
+            pw.print("  reason=" + InputMethodDebug.softInputDisplayReasonToString(
+                    entry.mReason));
+            pw.println(" inFullscreenMode=" + entry.mInFullscreenMode);
+
+            pw.print(prefix);
+            pw.println("  requestClient=" + entry.mClientState);
+
+            pw.print(prefix);
+            pw.println("  focusedWindowName=" + entry.mFocusedWindowName);
+
+            pw.print(prefix);
+            pw.println("  requestWindowName=" + entry.mRequestWindowName);
+
+            pw.print(prefix);
+            pw.println("  imeControlTargetName=" + entry.mImeControlTargetName);
+
+            pw.print(prefix);
+            pw.println("  imeTargetNameFromWm=" + entry.mImeTargetNameFromWm);
+
+            pw.print(prefix);
+            pw.println("  imeSurfaceParentName=" + entry.mImeSurfaceParentName);
+
+            pw.print(prefix);
+            pw.print("  editorInfo:");
+            if (entry.mEditorInfo != null) {
+                pw.print(" inputType=" + entry.mEditorInfo.inputType);
+                pw.print(" privateImeOptions=" + entry.mEditorInfo.privateImeOptions);
+                pw.println(" fieldId (viewId)=" + entry.mEditorInfo.fieldId);
+            } else {
+                pw.println(" null");
+            }
+
+            pw.print(prefix);
+            pw.println("  focusedWindowSoftInputMode=" + InputMethodDebug.softInputModeToString(
+                    entry.mFocusedWindowSoftInputMode));
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/StartInputHistory.java b/services/core/java/com/android/server/inputmethod/StartInputHistory.java
new file mode 100644
index 0000000..3a39434
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/StartInputHistory.java
@@ -0,0 +1,189 @@
+/*
+ * 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.app.ActivityManager;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.internal.inputmethod.InputMethodDebug;
+import com.android.internal.inputmethod.StartInputReason;
+
+import java.io.PrintWriter;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+
+/**
+ * A ring buffer to store the history of {@link StartInputInfo}.
+ */
+final class StartInputHistory {
+    /**
+     * Entry size for non low-RAM devices.
+     *
+     * <p>TODO: Consider to follow what other system services have been doing to manage
+     * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
+     */
+    private static final int ENTRY_SIZE_FOR_HIGH_RAM_DEVICE = 32;
+
+    /**
+     * Entry size for low-RAM devices.
+     *
+     * <p>TODO: Consider to follow what other system services have been doing to manage
+     * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
+     */
+    private static final int ENTRY_SIZE_FOR_LOW_RAM_DEVICE = 5;
+
+    private static int getEntrySize() {
+        if (ActivityManager.isLowRamDeviceStatic()) {
+            return ENTRY_SIZE_FOR_LOW_RAM_DEVICE;
+        } else {
+            return ENTRY_SIZE_FOR_HIGH_RAM_DEVICE;
+        }
+    }
+
+    /**
+     * Backing store for the ring buffer.
+     */
+    private final Entry[] mEntries = new Entry[getEntrySize()];
+
+    /**
+     * An index of {@link #mEntries}, to which next
+     * {@link #addEntry(StartInputInfo)} should
+     * write.
+     */
+    private int mNextIndex = 0;
+
+    /**
+     * Recyclable entry to store the information in {@link StartInputInfo}.
+     */
+    private static final class Entry {
+        int mSequenceNumber;
+        long mTimestamp;
+        long mWallTime;
+        @UserIdInt
+        int mImeUserId;
+        @NonNull
+        String mImeTokenString;
+        int mImeDisplayId;
+        @NonNull
+        String mImeId;
+        @StartInputReason
+        int mStartInputReason;
+        boolean mRestarting;
+        @UserIdInt
+        int mTargetUserId;
+        int mTargetDisplayId;
+        @NonNull
+        String mTargetWindowString;
+        @NonNull
+        EditorInfo mEditorInfo;
+        @WindowManager.LayoutParams.SoftInputModeFlags
+        int mTargetWindowSoftInputMode;
+        int mClientBindSequenceNumber;
+
+        Entry(@NonNull StartInputInfo original) {
+            set(original);
+        }
+
+        void set(@NonNull StartInputInfo original) {
+            mSequenceNumber = original.mSequenceNumber;
+            mTimestamp = original.mTimestamp;
+            mWallTime = original.mWallTime;
+            mImeUserId = original.mImeUserId;
+            // Intentionally convert to String so as not to keep a strong reference to a Binder
+            // object.
+            mImeTokenString = String.valueOf(original.mImeToken);
+            mImeDisplayId = original.mImeDisplayId;
+            mImeId = original.mImeId;
+            mStartInputReason = original.mStartInputReason;
+            mRestarting = original.mRestarting;
+            mTargetUserId = original.mTargetUserId;
+            mTargetDisplayId = original.mTargetDisplayId;
+            // Intentionally convert to String so as not to keep a strong reference to a Binder
+            // object.
+            mTargetWindowString = String.valueOf(original.mTargetWindow);
+            mEditorInfo = original.mEditorInfo;
+            mTargetWindowSoftInputMode = original.mTargetWindowSoftInputMode;
+            mClientBindSequenceNumber = original.mClientBindSequenceNumber;
+        }
+    }
+
+    /**
+     * Add a new entry and discard the oldest entry as needed.
+     *
+     * @param info {@link StartInputInfo} to be added.
+     */
+    void addEntry(@NonNull StartInputInfo info) {
+        final int index = mNextIndex;
+        if (mEntries[index] == null) {
+            mEntries[index] = new Entry(info);
+        } else {
+            mEntries[index].set(info);
+        }
+        mNextIndex = (mNextIndex + 1) % mEntries.length;
+    }
+
+    void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+        final DateTimeFormatter formatter =
+                DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
+                        .withZone(ZoneId.systemDefault());
+
+        for (int i = 0; i < mEntries.length; ++i) {
+            final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
+            if (entry == null) {
+                continue;
+            }
+            pw.print(prefix);
+            pw.println("StartInput #" + entry.mSequenceNumber + ":");
+
+            pw.print(prefix);
+            pw.println("  time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
+                    + " (timestamp=" + entry.mTimestamp + ")"
+                    + " reason="
+                    + InputMethodDebug.startInputReasonToString(entry.mStartInputReason)
+                    + " restarting=" + entry.mRestarting);
+
+            pw.print(prefix);
+            pw.print("  imeToken=" + entry.mImeTokenString + " [" + entry.mImeId + "]");
+            pw.print(" imeUserId=" + entry.mImeUserId);
+            pw.println(" imeDisplayId=" + entry.mImeDisplayId);
+
+            pw.print(prefix);
+            pw.println("  targetWin=" + entry.mTargetWindowString
+                    + " [" + entry.mEditorInfo.packageName + "]"
+                    + " targetUserId=" + entry.mTargetUserId
+                    + " targetDisplayId=" + entry.mTargetDisplayId
+                    + " clientBindSeq=" + entry.mClientBindSequenceNumber);
+
+            pw.print(prefix);
+            pw.println("  softInputMode=" + InputMethodDebug.softInputModeToString(
+                    entry.mTargetWindowSoftInputMode));
+
+            pw.print(prefix);
+            pw.println("  inputType=0x" + Integer.toHexString(entry.mEditorInfo.inputType)
+                    + " imeOptions=0x" + Integer.toHexString(entry.mEditorInfo.imeOptions)
+                    + " fieldId=0x" + Integer.toHexString(entry.mEditorInfo.fieldId)
+                    + " fieldName=" + entry.mEditorInfo.fieldName
+                    + " actionId=" + entry.mEditorInfo.actionId
+                    + " actionLabel=" + entry.mEditorInfo.actionLabel);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/StartInputInfo.java b/services/core/java/com/android/server/inputmethod/StartInputInfo.java
new file mode 100644
index 0000000..1cff737
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/StartInputInfo.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.inputmethodservice.InputMethodService;
+import android.os.IBinder;
+import android.os.SystemClock;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+
+import com.android.internal.inputmethod.IInputMethod;
+import com.android.internal.inputmethod.StartInputReason;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Internal state snapshot when
+ * {@link IInputMethod#startInput(IInputMethod.StartInputParams)} is about to be called.
+ *
+ * <p>Calling that IPC endpoint basically means that
+ * {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called
+ * back in the current IME process shortly, which will also affect what the current IME starts
+ * receiving from {@link InputMethodService#getCurrentInputConnection()}. In other words, this
+ * snapshot will be taken every time when {@link InputMethodManagerService} is initiating a new
+ * logical input session between the client application and the current IME.</p>
+ *
+ * <p>Be careful to not keep strong references to this object forever, which can prevent
+ * {@link StartInputInfo#mImeToken} and {@link StartInputInfo#mTargetWindow} from being GC-ed.
+ * </p>
+ */
+final class StartInputInfo {
+    private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
+
+    final int mSequenceNumber;
+    final long mTimestamp;
+    final long mWallTime;
+    @UserIdInt
+    final int mImeUserId;
+    @NonNull
+    final IBinder mImeToken;
+    final int mImeDisplayId;
+    @NonNull
+    final String mImeId;
+    @StartInputReason
+    final int mStartInputReason;
+    final boolean mRestarting;
+    @UserIdInt
+    final int mTargetUserId;
+    final int mTargetDisplayId;
+    @Nullable
+    final IBinder mTargetWindow;
+    @NonNull
+    final EditorInfo mEditorInfo;
+    @WindowManager.LayoutParams.SoftInputModeFlags
+    final int mTargetWindowSoftInputMode;
+    final int mClientBindSequenceNumber;
+
+    StartInputInfo(@UserIdInt int imeUserId, @NonNull IBinder imeToken, int imeDisplayId,
+            @NonNull String imeId, @StartInputReason int startInputReason, boolean restarting,
+            @UserIdInt int targetUserId, int targetDisplayId, @Nullable IBinder targetWindow,
+            @NonNull EditorInfo editorInfo,
+            @WindowManager.LayoutParams.SoftInputModeFlags int targetWindowSoftInputMode,
+            int clientBindSequenceNumber) {
+        mSequenceNumber = sSequenceNumber.getAndIncrement();
+        mTimestamp = SystemClock.uptimeMillis();
+        mWallTime = System.currentTimeMillis();
+        mImeUserId = imeUserId;
+        mImeToken = imeToken;
+        mImeDisplayId = imeDisplayId;
+        mImeId = imeId;
+        mStartInputReason = startInputReason;
+        mRestarting = restarting;
+        mTargetUserId = targetUserId;
+        mTargetDisplayId = targetDisplayId;
+        mTargetWindow = targetWindow;
+        mEditorInfo = editorInfo;
+        mTargetWindowSoftInputMode = targetWindowSoftInputMode;
+        mClientBindSequenceNumber = clientBindSequenceNumber;
+    }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index e7455db..e80c79a8 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -338,6 +338,7 @@
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.ConcurrentUtils;
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.util.function.TriPredicate;
@@ -740,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;
@@ -1825,6 +1826,12 @@
         }
     }
 
+    protected void logSensitiveAdjustmentReceived(boolean hasPosted,
+            boolean hasSensitiveContent, int lifespanMs) {
+        FrameworkStatsLog.write(FrameworkStatsLog.SENSITIVE_NOTIFICATION_REDACTION, hasPosted,
+                hasSensitiveContent, lifespanMs);
+    }
+
     @GuardedBy("mNotificationLock")
     void clearSoundLocked() {
         mSoundNotificationKey = null;
@@ -4868,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(),
@@ -6384,7 +6391,7 @@
                         if (Objects.equals(adjustment.getKey(), r.getKey())
                                 && Objects.equals(adjustment.getUser(), r.getUserId())
                                 && mAssistants.isSameUser(token, r.getUserId())) {
-                            applyAdjustment(r, adjustment);
+                            applyAdjustmentLocked(r, adjustment, false);
                             r.applyAdjustments();
                             // importance is checked at the beginning of the
                             // PostNotificationRunnable, before the signal extractors are run, so
@@ -6394,7 +6401,7 @@
                         }
                     }
                     if (!foundEnqueued) {
-                        applyAdjustmentFromAssistant(token, adjustment);
+                        applyAdjustmentsFromAssistant(token, List.of(adjustment));
                     }
                 }
             } finally {
@@ -6422,7 +6429,7 @@
                     for (Adjustment adjustment : adjustments) {
                         NotificationRecord r = mNotificationsByKey.get(adjustment.getKey());
                         if (r != null && mAssistants.isSameUser(token, r.getUserId())) {
-                            applyAdjustment(r, adjustment);
+                            applyAdjustmentLocked(r, adjustment, true);
                             // If the assistant has blocked the notification, cancel it
                             // This will trigger a sort, so we don't have to explicitly ask for
                             // one here.
@@ -6706,7 +6713,9 @@
         }
     }
 
-    private void applyAdjustment(NotificationRecord r, Adjustment adjustment) {
+    @GuardedBy("mNotificationLock")
+    private void applyAdjustmentLocked(NotificationRecord r, Adjustment adjustment,
+            boolean isPosted) {
         if (r == null) {
             return;
         }
@@ -6723,6 +6732,11 @@
                 adjustments.remove(removeKey);
             }
             r.addAdjustment(adjustment);
+            if (adjustment.getSignals().containsKey(Adjustment.KEY_SENSITIVE_CONTENT)) {
+                logSensitiveAdjustmentReceived(isPosted,
+                        adjustment.getSignals().getBoolean(Adjustment.KEY_SENSITIVE_CONTENT),
+                        r.getLifespanMs(System.currentTimeMillis()));
+            }
         }
     }
 
@@ -7870,8 +7884,6 @@
             }
         }
 
-        notification.overrideAllowlistToken(ALLOWLIST_TOKEN);
-
         // Remote views? Are they too big?
         checkRemoteViews(pkg, tag, id, notification);
     }
@@ -12054,10 +12066,17 @@
         @Override
         public void onServiceAdded(ManagedServiceInfo info) {
             if (lifetimeExtensionRefactor()) {
-                // We explicitly check the status bar permission for the uid in the info object.
-                // We can't use the calling uid here because it's probably always system server.
-                // Note that this will also be true for the shell.
-                info.isSystemUi = getContext().checkPermission(
+                // Generally, only System or System UI should have the permissions to call
+                // registerSystemService.
+                // isCallerSystemOrPhone tells us whether the caller is System. We negate this,
+                // to eliminate cases where the service was added by the system. This leaves
+                // services registered by system server.
+                // To identify system UI, we explicitly check the status bar permission for the
+                // uid in the info object.
+                // We can't use the calling uid here because it belongs to system server.
+                // Note that this will also return true for the shell, but we deem this
+                // acceptable, for the purposes of testing.
+                info.isSystemUi = !isCallerSystemOrPhone() && getContext().checkPermission(
                         android.Manifest.permission.STATUS_BAR_SERVICE, -1, info.uid)
                         == PERMISSION_GRANTED;
             }
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/permission/OWNERS b/services/core/java/com/android/server/permission/OWNERS
new file mode 100644
index 0000000..fb6099c
--- /dev/null
+++ b/services/core/java/com/android/server/permission/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 137825
+
+include platform/frameworks/base:/core/java/android/permission/OWNERS
diff --git a/services/core/java/com/android/server/permission/PermissionManagerLocal.java b/services/core/java/com/android/server/permission/PermissionManagerLocal.java
new file mode 100644
index 0000000..7251e6e
--- /dev/null
+++ b/services/core/java/com/android/server/permission/PermissionManagerLocal.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission;
+
+import android.annotation.TestApi;
+import com.android.internal.annotations.Keep;
+
+/**
+ * In-process API for server side permission related infrastructure.
+ *
+ * @hide
+ */
+@Keep
+@TestApi
+public interface PermissionManagerLocal {
+
+    /**
+     * Get whether signature permission allowlist is enforced even on debuggable builds.
+     *
+     * @return whether the signature permission allowlist is force enforced
+     */
+    @TestApi
+    boolean isSignaturePermissionAllowlistForceEnforced();
+
+    /**
+     * Set whether signature permission allowlist is enforced even on debuggable builds.
+     *
+     * @param forceEnforced whether the signature permission allowlist is force enforced
+     */
+    @TestApi
+    void setSignaturePermissionAllowlistForceEnforced(boolean forceEnforced);
+}
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index c7ebb3c..c6bb99e 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -2116,6 +2116,18 @@
 
         @RequiresPermission(READ_FRAME_BUFFER)
         @Override
+        public void saveViewCaptureData() {
+            int status = checkCallingOrSelfPermissionForPreflight(mContext, READ_FRAME_BUFFER);
+            if (PERMISSION_GRANTED == status) {
+                forEachViewCaptureWindow(this::dumpViewCaptureDataToWmTrace);
+            } else {
+                Log.w(TAG, "caller lacks permissions to save view capture data");
+            }
+        }
+
+
+        @RequiresPermission(READ_FRAME_BUFFER)
+        @Override
         public void registerDumpCallback(@NonNull IDumpCallback cb) {
             int status = checkCallingOrSelfPermissionForPreflight(mContext, READ_FRAME_BUFFER);
             if (PERMISSION_GRANTED == status) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerLocal.java b/services/core/java/com/android/server/pm/PackageManagerLocal.java
index 6266ef3..4ed6e80 100644
--- a/services/core/java/com/android/server/pm/PackageManagerLocal.java
+++ b/services/core/java/com/android/server/pm/PackageManagerLocal.java
@@ -20,6 +20,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.content.pm.SigningDetails;
 import android.os.Binder;
 import android.os.UserHandle;
 
@@ -124,6 +126,48 @@
     FilteredSnapshot withFilteredSnapshot(int callingUid, @NonNull UserHandle user);
 
     /**
+     * Add a pair of signing details so that packages signed with {@code oldSigningDetails} will
+     * behave as if they are signed by the {@code newSigningDetails}.
+     * <p>
+     * This is only available on {@link android.os.Build#isDebuggable debuggable} builds.
+     *
+     * @param oldSigningDetails the original signing detail of the package
+     * @param newSigningDetails the new signing detail that will replace the original one
+     * @throws SecurityException if the build is not debuggable
+     *
+     * @hide
+     */
+    @TestApi
+    void addOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails,
+            @NonNull SigningDetails newSigningDetails);
+
+    /**
+     * Remove a pair of signing details previously added via {@link #addOverrideSigningDetails} by
+     * the old signing details.
+     * <p>
+     * This is only available on {@link android.os.Build#isDebuggable debuggable} builds.
+     *
+     * @param oldSigningDetails the original signing detail of the package
+     * @throws SecurityException if the build is not debuggable
+     *
+     * @hide
+     */
+    @TestApi
+    void removeOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails);
+
+    /**
+     * Clear all pairs of signing details previously added via {@link #addOverrideSigningDetails}.
+     * <p>
+     * This is only available on {@link android.os.Build#isDebuggable debuggable} builds.
+     *
+     * @throws SecurityException if the build is not debuggable
+     *
+     * @hide
+     */
+    @TestApi
+    void clearOverrideSigningDetails();
+
+    /**
      * @hide
      */
     @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index c2f74a8..c9fd261 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -155,7 +155,8 @@
             UserManager.DISALLOW_CONFIG_DEFAULT_APPS,
             UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO,
             UserManager.DISALLOW_SIM_GLOBALLY,
-            UserManager.DISALLOW_ASSIST_CONTENT
+            UserManager.DISALLOW_ASSIST_CONTENT,
+            UserManager.DISALLOW_THREAD_NETWORK
     });
 
     public static final Set<String> DEPRECATED_USER_RESTRICTIONS = Sets.newArraySet(
@@ -206,7 +207,8 @@
             UserManager.DISALLOW_ADD_WIFI_CONFIG,
             UserManager.DISALLOW_CELLULAR_2G,
             UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO,
-            UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO
+            UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO,
+            UserManager.DISALLOW_THREAD_NETWORK
     );
 
     /**
@@ -252,7 +254,8 @@
                     UserManager.DISALLOW_ADD_WIFI_CONFIG,
                     UserManager.DISALLOW_CELLULAR_2G,
                     UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO,
-                    UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO
+                    UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO,
+                    UserManager.DISALLOW_THREAD_NETWORK
             );
 
     /**
diff --git a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
index 8d05450..55afb17 100644
--- a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
+++ b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
@@ -20,9 +20,12 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.content.pm.SigningDetails;
 import android.os.Binder;
+import android.os.Build;
 import android.os.UserHandle;
 import android.util.ArrayMap;
+import android.util.apk.ApkSignatureVerifier;
 
 import com.android.server.pm.Computer;
 import com.android.server.pm.PackageManagerLocal;
@@ -72,6 +75,31 @@
                 mService.snapshotComputer(false /*allowLiveComputer*/), null);
     }
 
+    @Override
+    public void addOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails,
+            @NonNull SigningDetails newSigningDetails) {
+        if (!Build.isDebuggable()) {
+            throw new SecurityException("This test API is only available on debuggable builds");
+        }
+        ApkSignatureVerifier.addOverrideSigningDetails(oldSigningDetails, newSigningDetails);
+    }
+
+    @Override
+    public void removeOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails) {
+        if (!Build.isDebuggable()) {
+            throw new SecurityException("This test API is only available on debuggable builds");
+        }
+        ApkSignatureVerifier.removeOverrideSigningDetails(oldSigningDetails);
+    }
+
+    @Override
+    public void clearOverrideSigningDetails() {
+        if (!Build.isDebuggable()) {
+            throw new SecurityException("This test API is only available on debuggable builds");
+        }
+        ApkSignatureVerifier.clearOverrideSigningDetails();
+    }
+
     private abstract static class BaseSnapshotImpl implements AutoCloseable {
 
         private boolean mClosed;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 5974ac8..266418f 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -4315,6 +4315,7 @@
             boolean allowDuringSetup) {
         if (allowDuringSetup || isUserSetupComplete()) {
             mContext.startActivityAsUser(intent, bundle, handle);
+            dismissKeyboardShortcutsMenu();
         } else {
             Slog.i(TAG, "Not starting activity because user setup is in progress: " + intent);
         }
@@ -4365,6 +4366,7 @@
         if (statusbar != null) {
             statusbar.showRecentApps(triggeredFromAltTab);
         }
+        dismissKeyboardShortcutsMenu();
     }
 
     private void toggleKeyboardShortcutsMenu(int deviceId) {
diff --git a/services/core/java/com/android/server/rollback/OWNERS b/services/core/java/com/android/server/rollback/OWNERS
index daa0211..8337fd2 100644
--- a/services/core/java/com/android/server/rollback/OWNERS
+++ b/services/core/java/com/android/server/rollback/OWNERS
@@ -1,3 +1 @@
-ancr@google.com
-harshitmahajan@google.com
-robertogil@google.com
+include /services/core/java/com/android/server/crashrecovery/OWNERS
\ No newline at end of file
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/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index c3efcb1..885baf6 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -2691,6 +2691,7 @@
                 }
 
                 float maxDimAmount = getHighestDimAmountFromMap(wallpaper.mUidToDimAmount);
+                if (wallpaper.mWallpaperDimAmount == maxDimAmount) return;
                 wallpaper.mWallpaperDimAmount = maxDimAmount;
                 // Also set the dim amount to the lock screen wallpaper if the lock and home screen
                 // do not share the same wallpaper
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 0069cdd..6fa6957 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -7786,8 +7786,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/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 071f403..f7baa79 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -18,10 +18,10 @@
 
 import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityOptions.BackgroundActivityStartMode;
 import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
 import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
 import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
-import static android.app.ActivityOptions.BackgroundActivityStartMode;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
@@ -37,12 +37,11 @@
 import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
 import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY;
 import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel;
+import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
 import static com.android.window.flags.Flags.balImproveRealCallerVisibilityCheck;
 import static com.android.window.flags.Flags.balRequireOptInByPendingIntentCreator;
 import static com.android.window.flags.Flags.balRequireOptInSameUid;
-import static com.android.window.flags.Flags.balShowToasts;
 import static com.android.window.flags.Flags.balShowToastsBlocked;
-import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 import static java.util.Objects.requireNonNull;
@@ -752,7 +751,6 @@
                 Slog.wtf(TAG, "With Android 15 BAL hardening this activity start may be blocked"
                         + " if the PI creator upgrades target_sdk to 35+! "
                         + " (missing opt in by PI creator)!" + state.dump());
-                showBalRiskToast();
                 return allowBasedOnCaller(state);
             }
         }
@@ -762,7 +760,6 @@
                 Slog.wtf(TAG, "With Android 14 BAL hardening this activity start will be blocked"
                         + " if the PI sender upgrades target_sdk to 34+! "
                         + " (missing opt in by PI sender)!" + state.dump());
-                showBalRiskToast();
                 return allowBasedOnRealCaller(state);
             }
         }
@@ -793,7 +790,11 @@
     private BalVerdict abortLaunch(BalState state) {
         Slog.wtf(TAG, "Background activity launch blocked! "
                 + state.dump());
-        showBalBlockedToast();
+        if (balShowToastsBlocked()
+                && (state.mResultForCaller.allows() || state.mResultForRealCaller.allows())) {
+            // only show a toast if either caller or real caller could launch if they opted in
+            showToast("BAL blocked. go/debug-bal");
+        }
         return statsLog(BalVerdict.BLOCK, state);
     }
 
@@ -1192,18 +1193,6 @@
         return true;
     }
 
-    private void showBalBlockedToast() {
-        if (balShowToastsBlocked()) {
-            showToast("BAL blocked. go/debug-bal");
-        }
-    }
-
-    private void showBalRiskToast() {
-        if (balShowToasts()) {
-            showToast("BAL allowed in compat mode. go/debug-bal");
-        }
-    }
-
     @VisibleForTesting void showToast(String toastText) {
         UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
                 toastText, Toast.LENGTH_LONG).show());
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index a914c07..b616d24 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -107,7 +107,9 @@
 
     ContentRecorder(@NonNull DisplayContent displayContent) {
         this(displayContent, new RemoteMediaProjectionManagerWrapper(displayContent.mDisplayId),
-                new DisplayManagerFlags().isConnectedDisplayManagementEnabled());
+                new DisplayManagerFlags().isConnectedDisplayManagementEnabled()
+                        && !new DisplayManagerFlags()
+                                    .isPixelAnisotropyCorrectionInLogicalDisplayEnabled());
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
index 877378c..a29cb60 100644
--- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
+++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
@@ -173,18 +173,25 @@
                     mDisplayContent.mInitialDisplayHeight);
             final int fromRotation = mDisplayContent.getRotation();
 
-            onStartCollect.run();
+            mDisplayContent.mAtmService.deferWindowLayout();
+            try {
+                onStartCollect.run();
 
-            ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
-                    "DeferredDisplayUpdater: applied DisplayInfo after deferring");
+                ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
+                        "DeferredDisplayUpdater: applied DisplayInfo after deferring");
 
-            if (physicalDisplayUpdated) {
-                onDisplayUpdated(transition, fromRotation, startBounds);
-            } else {
-                final TransitionRequestInfo.DisplayChange displayChange =
-                        getCurrentDisplayChange(fromRotation, startBounds);
-                mDisplayContent.mTransitionController.requestStartTransition(transition,
-                        /* startTask= */ null, /* remoteTransition= */ null, displayChange);
+                if (physicalDisplayUpdated) {
+                    onDisplayUpdated(transition, fromRotation, startBounds);
+                } else {
+                    final TransitionRequestInfo.DisplayChange displayChange =
+                            getCurrentDisplayChange(fromRotation, startBounds);
+                    mDisplayContent.mTransitionController.requestStartTransition(transition,
+                            /* startTask= */ null, /* remoteTransition= */ null, displayChange);
+                }
+            } finally {
+                // Run surface placement after requestStartTransition, so shell side can receive
+                // the transition request before handling task info changes.
+                mDisplayContent.mAtmService.continueWindowLayout();
             }
         });
     }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 282ecc7..837d08b 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -6210,7 +6210,12 @@
      * @param onDisplayChangeApplied callback that is called when the changes are applied
      */
     void requestDisplayUpdate(@NonNull Runnable onDisplayChangeApplied) {
-        mDisplayUpdater.updateDisplayInfo(onDisplayChangeApplied);
+        mAtmService.deferWindowLayout();
+        try {
+            mDisplayUpdater.updateDisplayInfo(onDisplayChangeApplied);
+        } finally {
+            mAtmService.continueWindowLayout();
+        }
     }
 
     void onDisplayInfoUpdated(@NonNull DisplayInfo newDisplayInfo) {
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/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index a63e106..7e6f5ac 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(
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 c37946b..ee72db0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3395,7 +3395,9 @@
                     if (shouldMigrateV1ToDevicePolicyEngine()) {
                         migrateV1PoliciesToDevicePolicyEngine();
                     }
+                    maybeMigratePoliciesPostUpgradeToDevicePolicyEngineLocked();
                     migratePoliciesToPolicyEngineLocked();
+
                 }
                 maybeStartSecurityLogMonitorOnActivityManagerReady();
                 break;
@@ -16877,6 +16879,8 @@
     private int checkDeviceOwnerProvisioningPreCondition(@UserIdInt int callingUserId) {
         synchronized (getLockObject()) {
             final int deviceOwnerUserId = mInjector.userManagerIsHeadlessSystemUserMode()
+                    && (!Flags.headlessDeviceOwnerProvisioningFixEnabled()
+                    || getHeadlessDeviceOwnerMode() == HEADLESS_DEVICE_OWNER_MODE_AFFILIATED)
                     ? UserHandle.USER_SYSTEM
                     : callingUserId;
             Slogf.i(LOG_TAG, "Calling user %d, device owner will be set on user %d",
@@ -21549,10 +21553,21 @@
             setTimeAndTimezone(provisioningParams.getTimeZone(), provisioningParams.getLocalTime());
             setLocale(provisioningParams.getLocale());
 
+
+
+            boolean isSingleUserMode;
+            if (Flags.headlessDeviceOwnerProvisioningFixEnabled()) {
+                DeviceAdminInfo adminInfo = findAdmin(
+                        deviceAdmin, caller.getUserId(), /* throwForMissingPermission= */ false);
+                isSingleUserMode = (adminInfo != null && adminInfo.getHeadlessDeviceOwnerMode()
+                        == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER);
+            } else {
+                isSingleUserMode =
+                        (getHeadlessDeviceOwnerMode() == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER);
+            }
             int deviceOwnerUserId = Flags.headlessDeviceOwnerSingleUserEnabled()
-                    && getHeadlessDeviceOwnerMode() == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER
-                    ? mUserManagerInternal.getMainUserId()
-                    : UserHandle.USER_SYSTEM;
+                    && isSingleUserMode
+                    ? mUserManagerInternal.getMainUserId() : UserHandle.USER_SYSTEM;
 
             if (!removeNonRequiredAppsForManagedDevice(
                     deviceOwnerUserId,
@@ -23736,7 +23751,9 @@
             if (!canForceMigration && !shouldMigrateV1ToDevicePolicyEngine()) {
                 return false;
             }
-            return migrateV1PoliciesToDevicePolicyEngine();
+            boolean migrated = migrateV1PoliciesToDevicePolicyEngine();
+            migrated &= migratePoliciesPostUpgradeToDevicePolicyEngineLocked();
+            return migrated;
         });
     }
 
@@ -23765,6 +23782,30 @@
 
     /**
      * Migrates the initial set of policies to use policy engine.
+     * [b/318497672] Migrate policies that weren't migrated properly in the initial migration on
+     * update from Android T to Android U
+     */
+    private void maybeMigratePoliciesPostUpgradeToDevicePolicyEngineLocked() {
+        if (!mOwners.isMigratedToPolicyEngine() || mOwners.isMigratedPostUpdate()) {
+            return;
+        }
+        migratePoliciesPostUpgradeToDevicePolicyEngineLocked();
+        mOwners.markPostUpgradeMigration();
+    }
+
+    private boolean migratePoliciesPostUpgradeToDevicePolicyEngineLocked() {
+        try {
+            migrateScreenCapturePolicyLocked();
+            migrateLockTaskPolicyLocked();
+            return true;
+        } catch (Exception e) {
+            Slogf.e(LOG_TAG, e, "Error occurred during post upgrade migration to the device "
+                    + "policy engine.");
+            return false;
+        }
+    }
+
+    /**
      * @return {@code true} if policies were migrated successfully, {@code false} otherwise.
      */
     private boolean migrateV1PoliciesToDevicePolicyEngine() {
@@ -23777,7 +23818,6 @@
                         migrateAutoTimezonePolicy();
                         migratePermissionGrantStatePolicies();
                     }
-                    migrateScreenCapturePolicyLocked();
                     migratePermittedInputMethodsPolicyLocked();
                     migrateAccountManagementDisabledPolicyLocked();
                     migrateUserControlDisabledPackagesLocked();
@@ -23858,14 +23898,12 @@
 
     private void migrateScreenCapturePolicyLocked() {
         Binder.withCleanCallingIdentity(() -> {
-            if (mPolicyCache.getScreenCaptureDisallowedUser() == UserHandle.USER_NULL) {
-                return;
-            }
             ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
             if (admin != null
                     && ((isDeviceOwner(admin) && admin.disableScreenCapture)
                     || (admin.getParentActiveAdmin() != null
                     && admin.getParentActiveAdmin().disableScreenCapture))) {
+
                 EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
                         admin.info.getComponent(),
                         admin.getUserHandle().getIdentifier(),
@@ -23894,6 +23932,48 @@
         });
     }
 
+    private void migrateLockTaskPolicyLocked() {
+        Binder.withCleanCallingIdentity(() -> {
+            ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+            if (deviceOwner != null) {
+                int doUserId = deviceOwner.getUserHandle().getIdentifier();
+                DevicePolicyData policies = getUserData(doUserId);
+                List<String> packages = policies.mLockTaskPackages;
+                int features = policies.mLockTaskFeatures;
+                // TODO: find out about persistent preferred activities
+                if (!packages.isEmpty()) {
+                    setLockTaskPolicyInPolicyEngine(deviceOwner, doUserId, packages, features);
+                }
+            }
+
+            for (int userId : mUserManagerInternal.getUserIds()) {
+                ActiveAdmin profileOwner = getProfileOwnerLocked(userId);
+                if (profileOwner != null && canDPCManagedUserUseLockTaskLocked(userId)) {
+                    DevicePolicyData policies = getUserData(userId);
+                    List<String> packages = policies.mLockTaskPackages;
+                    int features = policies.mLockTaskFeatures;
+                    if (!packages.isEmpty()) {
+                        setLockTaskPolicyInPolicyEngine(profileOwner, userId, packages, features);
+                    }
+                }
+            }
+        });
+    }
+
+    private void setLockTaskPolicyInPolicyEngine(
+            ActiveAdmin admin, int userId, List<String> packages, int features) {
+        EnforcingAdmin enforcingAdmin =
+                EnforcingAdmin.createEnterpriseEnforcingAdmin(
+                        admin.info.getComponent(),
+                        userId,
+                        admin);
+        mDevicePolicyEngine.setLocalPolicy(
+                PolicyDefinition.LOCK_TASK,
+                enforcingAdmin,
+                new LockTaskPolicy(new HashSet<>(packages), features),
+                userId);
+    }
+
     private void migratePermittedInputMethodsPolicyLocked() {
         Binder.withCleanCallingIdentity(() -> {
             List<UserInfo> users = mUserManager.getUsers();
@@ -24256,4 +24336,13 @@
 
         return mDevicePolicyEngine.getMaxPolicyStorageLimit();
     }
+
+    @Override
+    public int getHeadlessDeviceOwnerMode(String callerPackageName) {
+        final CallerIdentity caller = getCallerIdentity(callerPackageName);
+        enforcePermission(MANAGE_PROFILE_AND_DEVICE_OWNERS, caller.getPackageName(),
+                caller.getUserId());
+
+        return Binder.withCleanCallingIdentity(() -> getHeadlessDeviceOwnerMode());
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index c5a9888..7912cbc 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -623,12 +623,25 @@
         }
     }
 
+    void markPostUpgradeMigration() {
+        synchronized (mData) {
+            mData.mPoliciesMigratedPostUpdate = true;
+            mData.writeDeviceOwner();
+        }
+    }
+
     boolean isSecurityLoggingMigrated() {
         synchronized (mData) {
             return mData.mSecurityLoggingMigrated;
         }
     }
 
+    boolean isMigratedPostUpdate() {
+        synchronized (mData) {
+            return mData.mPoliciesMigratedPostUpdate;
+        }
+    }
+
     @GuardedBy("mData")
     void pushToAppOpsLocked() {
         if (!mSystemReady) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
index 9d73ed0..42ac998 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
@@ -89,6 +89,8 @@
     private static final String ATTR_MIGRATED_TO_POLICY_ENGINE = "migratedToPolicyEngine";
     private static final String ATTR_SECURITY_LOG_MIGRATED = "securityLogMigrated";
 
+    private static final String ATTR_MIGRATED_POST_UPGRADE = "migratedPostUpgrade";
+
     // Internal state for the device owner package.
     OwnerInfo mDeviceOwner;
     int mDeviceOwnerUserId = UserHandle.USER_NULL;
@@ -117,6 +119,8 @@
     boolean mMigratedToPolicyEngine = false;
     boolean mSecurityLoggingMigrated = false;
 
+    boolean mPoliciesMigratedPostUpdate = false;
+
     OwnersData(PolicyPathProvider pathProvider) {
         mPathProvider = pathProvider;
     }
@@ -400,6 +404,7 @@
 
             out.startTag(null, TAG_POLICY_ENGINE_MIGRATION);
             out.attributeBoolean(null, ATTR_MIGRATED_TO_POLICY_ENGINE, mMigratedToPolicyEngine);
+            out.attributeBoolean(null, ATTR_MIGRATED_POST_UPGRADE, mPoliciesMigratedPostUpdate);
             if (Flags.securityLogV2Enabled()) {
                 out.attributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, mSecurityLoggingMigrated);
             }
@@ -463,8 +468,11 @@
                 case TAG_POLICY_ENGINE_MIGRATION:
                     mMigratedToPolicyEngine = parser.getAttributeBoolean(
                             null, ATTR_MIGRATED_TO_POLICY_ENGINE, false);
+                    mPoliciesMigratedPostUpdate = parser.getAttributeBoolean(
+                            null, ATTR_MIGRATED_POST_UPGRADE, false);
                     mSecurityLoggingMigrated = Flags.securityLogV2Enabled()
                             && parser.getAttributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, false);
+
                     break;
                 default:
                     Slog.e(TAG, "Unexpected tag: " + tag);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 71facab..e713a82 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -506,6 +506,10 @@
                 UserManager.DISALLOW_SIM_GLOBALLY,
                 POLICY_FLAG_GLOBAL_ONLY_POLICY);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ASSIST_CONTENT, /* flags= */ 0);
+        if (com.android.net.thread.platform.flags.Flags.threadUserRestrictionEnabled()) {
+            USER_RESTRICTION_FLAGS.put(
+                    UserManager.DISALLOW_THREAD_NETWORK, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+        }
 
         for (String key : USER_RESTRICTION_FLAGS.keySet()) {
             createAndAddUserRestrictionPolicyDefinition(key, USER_RESTRICTION_FLAGS.get(key));
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 2112dae..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;
@@ -1134,7 +1135,7 @@
         ServiceManager.addService(Context.PLATFORM_COMPAT_SERVICE, platformCompat);
         ServiceManager.addService(Context.PLATFORM_COMPAT_NATIVE_SERVICE,
                 new PlatformCompatNative(platformCompat));
-        AppCompatCallbacks.install(new long[0]);
+        AppCompatCallbacks.install(new long[0], new long[0]);
         t.traceEnd();
 
         // FileIntegrityService responds to requests from apps and the system. It needs to run after
@@ -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/AccessCheckingService.kt b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
index acaec21..fd2e8c8 100644
--- a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
@@ -27,9 +27,11 @@
 import com.android.server.SystemConfig
 import com.android.server.SystemService
 import com.android.server.appop.AppOpsCheckingServiceInterface
+import com.android.server.permission.PermissionManagerLocal
 import com.android.server.permission.access.appop.AppOpService
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
 import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.permission.PermissionManagerLocalImpl
 import com.android.server.permission.access.permission.PermissionService
 import com.android.server.pm.KnownPackages
 import com.android.server.pm.PackageManagerLocal
@@ -63,6 +65,11 @@
 
         LocalServices.addService(AppOpsCheckingServiceInterface::class.java, appOpService)
         LocalServices.addService(PermissionManagerServiceInterface::class.java, permissionService)
+
+        LocalManagerRegistry.addManager(
+            PermissionManagerLocal::class.java,
+            PermissionManagerLocalImpl(this)
+        )
     }
 
     fun initialize() {
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 67df67f..af8ce31 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
@@ -63,6 +63,12 @@
 
     private val privilegedPermissionAllowlistViolations = MutableIndexedSet<String>()
 
+    /**
+     * Test-only switch to enforce signature permission allowlist even on debuggable builds.
+     */
+    @Volatile
+    var isSignaturePermissionAllowlistForceEnforced = false
+
     override val subjectScheme: String
         get() = UidUri.SCHEME
 
@@ -1274,7 +1280,7 @@
                     SigningDetails.CertCapabilities.PERMISSION
                 )
         if (!Flags.signaturePermissionAllowlistEnabled()) {
-            return hasCommonSigner;
+            return hasCommonSigner
         }
         if (!hasCommonSigner) {
             return false
@@ -1308,7 +1314,7 @@
                         " ${packageState.packageName} (${packageState.path}) not in" +
                         " signature permission allowlist"
                 )
-                if (!Build.isDebuggable()) {
+                if (!Build.isDebuggable() || isSignaturePermissionAllowlistForceEnforced) {
                     return false
                 }
             }
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionManagerLocalImpl.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionManagerLocalImpl.kt
new file mode 100644
index 0000000..ad2d70bb
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionManagerLocalImpl.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.permission.access.permission
+
+import android.os.Build
+import com.android.server.permission.PermissionManagerLocal
+import com.android.server.permission.access.AccessCheckingService
+import com.android.server.permission.access.PermissionUri
+import com.android.server.permission.access.UidUri
+
+class PermissionManagerLocalImpl(
+    private val service: AccessCheckingService
+) : PermissionManagerLocal {
+    private val policy =
+        service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as AppIdPermissionPolicy
+
+    override fun isSignaturePermissionAllowlistForceEnforced(): Boolean {
+        check(Build.isDebuggable())
+        return policy.isSignaturePermissionAllowlistForceEnforced
+    }
+
+    override fun setSignaturePermissionAllowlistForceEnforced(forceEnforced: Boolean) {
+        check(Build.isDebuggable())
+        policy.isSignaturePermissionAllowlistForceEnforced = forceEnforced
+    }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
index a33e52f..e5d3153 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
@@ -91,8 +91,8 @@
     @Test
     public void testSoftInputShowHideHistoryDump_withNulls_doesntThrow() {
         var writer = new StringWriter();
-        var history = new InputMethodManagerService.SoftInputShowHideHistory();
-        history.addEntry(new InputMethodManagerService.SoftInputShowHideHistory.Entry(
+        var history = new SoftInputShowHideHistory();
+        history.addEntry(new SoftInputShowHideHistory.Entry(
                 null,
                 null,
                 null,
diff --git a/services/tests/VpnTests/Android.bp b/services/tests/VpnTests/Android.bp
index 64a9a3b..a5011a8 100644
--- a/services/tests/VpnTests/Android.bp
+++ b/services/tests/VpnTests/Android.bp
@@ -17,8 +17,7 @@
         "java/**/*.java",
         "java/**/*.kt",
     ],
-
-    defaults: ["framework-connectivity-test-defaults"],
+    sdk_version: "core_platform", // tests can use @CorePlatformApi's
     test_suites: ["device-tests"],
     static_libs: [
         "androidx.test.rules",
@@ -32,6 +31,13 @@
         "service-connectivity-tiramisu-pre-jarjar",
     ],
     libs: [
+        // order matters: classes in framework-connectivity are resolved before framework,
+        // meaning @hide APIs in framework-connectivity are resolved before @SystemApi
+        // stubs in framework
+        "framework-connectivity.impl",
+        "framework-connectivity-t.impl",
+        "framework",
+        "framework-res",
         "android.test.runner",
         "android.test.base",
         "android.test.mock",
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/DisplayDeviceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
index dc6abf1..1c71abc 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
@@ -52,7 +52,9 @@
     private static final int WIDTH = 500;
     private static final int HEIGHT = 900;
     private static final Point PORTRAIT_SIZE = new Point(WIDTH, HEIGHT);
+    private static final Point PORTRAIT_DOUBLE_WIDTH = new Point(2 * WIDTH, HEIGHT);
     private static final Point LANDSCAPE_SIZE = new Point(HEIGHT, WIDTH);
+    private static final Point LANDSCAPE_DOUBLE_HEIGHT = new Point(HEIGHT, 2 * WIDTH);
 
     @Mock
     private SurfaceControl.Transaction mMockTransaction;
@@ -69,6 +71,16 @@
     }
 
     @Test
+    public void testGetDisplaySurfaceDefaultSizeLocked_notRotated_anisotropyCorrection() {
+        mDisplayDeviceInfo.xDpi = 0.5f;
+        mDisplayDeviceInfo.yDpi = 1.0f;
+        DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
+                mMockDisplayAdapter, /*isAnisotropyCorrectionEnabled=*/ true);
+        assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(
+                PORTRAIT_DOUBLE_WIDTH);
+    }
+
+    @Test
     public void testGetDisplaySurfaceDefaultSizeLocked_notRotated() {
         DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
                 mMockDisplayAdapter);
@@ -84,6 +96,17 @@
     }
 
     @Test
+    public void testGetDisplaySurfaceDefaultSizeLocked_rotation90_anisotropyCorrection() {
+        mDisplayDeviceInfo.xDpi = 0.5f;
+        mDisplayDeviceInfo.yDpi = 1.0f;
+        DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
+                mMockDisplayAdapter, /*isAnisotropyCorrectionEnabled=*/ true);
+        displayDevice.setProjectionLocked(mMockTransaction, ROTATION_90, new Rect(), new Rect());
+        assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(
+                LANDSCAPE_DOUBLE_HEIGHT);
+    }
+
+    @Test
     public void testGetDisplaySurfaceDefaultSizeLocked_rotation90() {
         DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
                 mMockDisplayAdapter);
@@ -111,8 +134,14 @@
         private final DisplayDeviceInfo mDisplayDeviceInfo;
 
         FakeDisplayDevice(DisplayDeviceInfo displayDeviceInfo, DisplayAdapter displayAdapter) {
+            this(displayDeviceInfo, displayAdapter, /*isAnisotropyCorrectionEnabled=*/ false);
+        }
+
+        FakeDisplayDevice(DisplayDeviceInfo displayDeviceInfo, DisplayAdapter displayAdapter,
+                boolean isAnisotropyCorrectionEnabled) {
             super(displayAdapter, /* displayToken= */ null, /* uniqueId= */ "",
-                    InstrumentationRegistry.getInstrumentation().getContext());
+                    InstrumentationRegistry.getInstrumentation().getContext(),
+                    isAnisotropyCorrectionEnabled);
             mDisplayDeviceInfo = displayDeviceInfo;
         }
 
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..18f0311 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -2408,6 +2408,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 +2441,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 +2489,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 +2655,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 +2703,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/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
index 1c43418..549f0d7 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
@@ -106,8 +106,184 @@
     }
 
     @Test
+    public void testLetterbox() {
+        mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+                /*isAnisotropyCorrectionEnabled=*/ false);
+        mDisplayDeviceInfo.xDpi = 0.5f;
+        mDisplayDeviceInfo.yDpi = 1.0f;
+
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        var originalDisplayInfo = mLogicalDisplay.getDisplayInfoLocked();
+        assertEquals(DISPLAY_WIDTH, originalDisplayInfo.logicalWidth);
+        assertEquals(DISPLAY_HEIGHT, originalDisplayInfo.logicalHeight);
+
+        SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+        assertEquals(new Point(0, 0), mLogicalDisplay.getDisplayPosition());
+
+        /*
+         * Content is too wide, should become letterboxed
+         *  ______DISPLAY_WIDTH________
+         * |                        |
+         * |________________________|
+         * |                        |
+         * |       CONTENT          |
+         * |                        |
+         * |________________________|
+         * |                        |
+         * |________________________|
+         */
+        // Make a wide application content, by reducing its height.
+        DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.logicalWidth = DISPLAY_WIDTH;
+        displayInfo.logicalHeight = DISPLAY_HEIGHT / 2;
+        mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
+
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+        assertEquals(new Point(0, DISPLAY_HEIGHT / 4), mLogicalDisplay.getDisplayPosition());
+    }
+
+    @Test
+    public void testNoLetterbox_anisotropyCorrection() {
+        mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+                /*isAnisotropyCorrectionEnabled=*/ true);
+
+        // In case of Anisotropy of pixels, then the content should be rescaled so it would adjust
+        // to using the whole screen. This is because display will rescale it back to fill the
+        // screen (in case the display menu setting is set to stretch the pixels across the display)
+        mDisplayDeviceInfo.xDpi = 0.5f;
+        mDisplayDeviceInfo.yDpi = 1.0f;
+
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        var originalDisplayInfo = mLogicalDisplay.getDisplayInfoLocked();
+        // Content width re-scaled
+        assertEquals(DISPLAY_WIDTH * 2, originalDisplayInfo.logicalWidth);
+        assertEquals(DISPLAY_HEIGHT, originalDisplayInfo.logicalHeight);
+
+        SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+
+        // Applications need to think that they are shown on a display with square pixels.
+        // as applications can be displayed on multiple displays simultaneously (mirrored).
+        // Content is too wide, should have become letterboxed - but it won't because of anisotropy
+        // correction
+        assertEquals(new Point(0, 0), mLogicalDisplay.getDisplayPosition());
+    }
+
+    @Test
+    public void testLetterbox_anisotropyCorrectionYDpi() {
+        mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+                /*isAnisotropyCorrectionEnabled=*/ true);
+
+        DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.logicalWidth = DISPLAY_WIDTH;
+        displayInfo.logicalHeight = DISPLAY_HEIGHT / 2;
+        mDisplayDeviceInfo.xDpi = 1.0f;
+        mDisplayDeviceInfo.yDpi = 0.5f;
+        mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+
+        SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+
+        assertEquals(new Point(0, 75), mLogicalDisplay.getDisplayPosition());
+    }
+
+    @Test
+    public void testPillarbox() {
+        mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+                /*isAnisotropyCorrectionEnabled=*/ false);
+        mDisplayDeviceInfo.xDpi = 0.5f;
+        mDisplayDeviceInfo.yDpi = 1.0f;
+
+        DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.rotation = Surface.ROTATION_90;
+        displayInfo.logicalWidth = DISPLAY_WIDTH;
+        displayInfo.logicalHeight = DISPLAY_HEIGHT;
+        mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
+        mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+
+        var updatedDisplayInfo = mLogicalDisplay.getDisplayInfoLocked();
+        assertEquals(Surface.ROTATION_90, updatedDisplayInfo.rotation);
+        assertEquals(DISPLAY_WIDTH, updatedDisplayInfo.logicalWidth);
+        assertEquals(DISPLAY_HEIGHT, updatedDisplayInfo.logicalHeight);
+
+        /*
+         * Content is too tall, should become pillarboxed
+         *  ______DISPLAY_WIDTH________
+         * |    |                |    |
+         * |    |                |    |
+         * |    |                |    |
+         * |    |   CONTENT      |    |
+         * |    |                |    |
+         * |    |                |    |
+         * |____|________________|____|
+         */
+
+        SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+
+        assertEquals(new Point(75, 0), mLogicalDisplay.getDisplayPosition());
+    }
+
+    @Test
+    public void testPillarbox_anisotropyCorrection() {
+        mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+                /*isAnisotropyCorrectionEnabled=*/ true);
+
+        DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.logicalWidth = DISPLAY_WIDTH;
+        displayInfo.logicalHeight = DISPLAY_HEIGHT;
+        displayInfo.rotation = Surface.ROTATION_90;
+        mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
+        // In case of Anisotropy of pixels, then the content should be rescaled so it would adjust
+        // to using the whole screen. This is because display will rescale it back to fill the
+        // screen (in case the display menu setting is set to stretch the pixels across the display)
+        mDisplayDeviceInfo.xDpi = 0.5f;
+        mDisplayDeviceInfo.yDpi = 1.0f;
+        mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+
+        SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+
+        // Applications need to think that they are shown on a display with square pixels.
+        // as applications can be displayed on multiple displays simultaneously (mirrored).
+        // Content is a bit wider than in #testPillarbox, due to content added stretching
+        assertEquals(new Point(50, 0), mLogicalDisplay.getDisplayPosition());
+    }
+
+    @Test
+    public void testNoPillarbox_anisotropyCorrectionYDpi() {
+        mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+                /*isAnisotropyCorrectionEnabled=*/ true);
+
+        // In case of Anisotropy of pixels, then the content should be rescaled so it would adjust
+        // to using the whole screen. This is because display will rescale it back to fill the
+        // screen (in case the display menu setting is set to stretch the pixels across the display)
+        mDisplayDeviceInfo.xDpi = 1.0f;
+        mDisplayDeviceInfo.yDpi = 0.5f;
+
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        var originalDisplayInfo = mLogicalDisplay.getDisplayInfoLocked();
+        // Content width re-scaled
+        assertEquals(DISPLAY_WIDTH, originalDisplayInfo.logicalWidth);
+        assertEquals(DISPLAY_HEIGHT * 2, originalDisplayInfo.logicalHeight);
+
+        SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+
+        // Applications need to think that they are shown on a display with square pixels.
+        // as applications can be displayed on multiple displays simultaneously (mirrored).
+        // Content is too tall, should have occupy the whole screen - but it won't because of
+        // anisotropy correction
+        assertEquals(new Point(0, 0), mLogicalDisplay.getDisplayPosition());
+    }
+
+    @Test
     public void testGetDisplayPosition() {
-        Point expectedPosition = new Point();
+        Point expectedPosition = new Point(0, 0);
 
         SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
         mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index 64076e6..3eced7f 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -1631,12 +1631,25 @@
         director.start(sensorManager);
         director.injectSupportedModesByDisplay(supportedModesByDisplay);
 
-        setPeakRefreshRate(Float.POSITIVE_INFINITY);
+        // Disable Smooth Display
+        setPeakRefreshRate(RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
 
         Vote vote1 = director.getVote(DISPLAY_ID,
                 Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
         Vote vote2 = director.getVote(DISPLAY_ID_2,
                 Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0,
+                /* frameRateHigh= */ RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+        assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0,
+                /* frameRateHigh= */ RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+
+        // Enable Smooth Display
+        setPeakRefreshRate(Float.POSITIVE_INFINITY);
+
+        vote1 = director.getVote(DISPLAY_ID,
+                Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+        vote2 = director.getVote(DISPLAY_ID_2,
+                Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
         assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0, /* frameRateHigh= */ 130);
         assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0, /* frameRateHigh= */ 140);
     }
@@ -1654,10 +1667,18 @@
         SensorManager sensorManager = createMockSensorManager(lightSensor);
         director.start(sensorManager);
 
-        setPeakRefreshRate(peakRefreshRate);
+        // Disable Smooth Display
+        setPeakRefreshRate(RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
 
         Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
         assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0,
+                /* frameRateHigh= */ RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+
+        // Enable Smooth Display
+        setPeakRefreshRate(peakRefreshRate);
+
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0,
                 /* frameRateHigh= */ peakRefreshRate);
     }
 
@@ -1759,11 +1780,23 @@
         director.start(sensorManager);
         director.injectSupportedModesByDisplay(supportedModesByDisplay);
 
-        setMinRefreshRate(Float.POSITIVE_INFINITY);
+        // Disable Force Peak Refresh Rate
+        setMinRefreshRate(0);
 
         Vote vote1 = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
         Vote vote2 = director.getVote(DISPLAY_ID_2,
                 Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0,
+                /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+        assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0,
+                /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+
+        // Enable Force Peak Refresh Rate
+        setMinRefreshRate(Float.POSITIVE_INFINITY);
+
+        vote1 = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+        vote2 = director.getVote(DISPLAY_ID_2,
+                Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
         assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 130,
                 /* frameRateHigh= */ Float.POSITIVE_INFINITY);
         assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 140,
@@ -1783,9 +1816,17 @@
         SensorManager sensorManager = createMockSensorManager(lightSensor);
         director.start(sensorManager);
 
-        setMinRefreshRate(minRefreshRate);
+        // Disable Force Peak Refresh Rate
+        setMinRefreshRate(0);
 
         Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0,
+                /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+
+        // Enable Force Peak Refresh Rate
+        setMinRefreshRate(minRefreshRate);
+
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
         assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ minRefreshRate,
                 /* frameRateHigh= */ Float.POSITIVE_INFINITY);
     }
@@ -1829,6 +1870,58 @@
     }
 
     @Test
+    public void testPeakAndMinRefreshRate_FlagEnabled_DisplayWithOneMode() {
+        when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
+                .thenReturn(true);
+        DisplayModeDirector director =
+                new DisplayModeDirector(mContext, mHandler, mInjector, mDisplayManagerFlags);
+        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+
+        Display.Mode[] modes1 = new Display.Mode[] {
+                new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
+                        /* refreshRate= */ 60),
+                new Display.Mode(/* modeId= */ 2, /* width= */ 1280, /* height= */ 720,
+                        /* refreshRate= */ 130),
+        };
+        Display.Mode[] modes2 = new Display.Mode[] {
+                new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
+                        /* refreshRate= */ 60),
+        };
+        SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>();
+        supportedModesByDisplay.put(DISPLAY_ID, modes1);
+        supportedModesByDisplay.put(DISPLAY_ID_2, modes2);
+
+        Sensor lightSensor = createLightSensor();
+        SensorManager sensorManager = createMockSensorManager(lightSensor);
+        director.start(sensorManager);
+        director.injectSupportedModesByDisplay(supportedModesByDisplay);
+
+        // Disable Force Peak Refresh Rate and Smooth Display
+        setMinRefreshRate(0);
+        setPeakRefreshRate(RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+
+        // Even though the highest refresh rate of the second display == the current min refresh
+        // rate == 60, Force Peak Refresh Rate should remain disabled
+        Vote vote1 = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+        Vote vote2 = director.getVote(DISPLAY_ID_2,
+                Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0,
+                /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+        assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0,
+                /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+
+        // Even though the highest refresh rate of the second display == the current peak refresh
+        // rate == 60, Smooth Display should remain disabled
+        vote1 = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+        vote2 = director.getVote(DISPLAY_ID_2,
+                Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0,
+                /* frameRateHigh= */ RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+        assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0,
+                /* frameRateHigh= */ RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+    }
+
+    @Test
     public void testSensorRegistration() {
         // First, configure brightness zones or DMD won't register for sensor data.
         final FakeDeviceConfig config = mInjector.getDeviceConfig();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
index caa0864..a8b792e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
@@ -213,7 +213,7 @@
                 any(), any(), any(),
                 any(), any(),
                 any(), any(),
-                any(),
+                any(), any(),
                 anyLong(), anyLong());
 
         final ProcessRecord r = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid));
@@ -277,7 +277,7 @@
                 null, null,
                 null,
                 null, null, null,
-                null, null,
+                null, null, null,
                 0, 0);
 
         // Sleep until timeout should have triggered
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
index bd20ae2..ce281da 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
@@ -56,6 +56,7 @@
 
 import org.junit.Rule;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 import java.io.File;
@@ -160,6 +161,7 @@
         realAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
         realAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
         realAms.mAtmInternal = spy(realAms.mActivityTaskManager.getAtmInternal());
+        realAms.mOomAdjuster.mCachedAppOptimizer = Mockito.mock(CachedAppOptimizer.class);
         realAms.mOomAdjuster = spy(realAms.mOomAdjuster);
         ExtendedMockito.doNothing().when(() -> ProcessList.setOomAdj(anyInt(), anyInt(), anyInt()));
         realAms.mPackageManagerInt = mPackageManagerInt;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 66ab807..1172685 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -86,7 +86,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.mockito.ArgumentMatcher;
 import org.mockito.InOrder;
@@ -2335,8 +2334,8 @@
                 .isGreaterThan(getReceiverScheduledTime(prioritizedRecord, receiverBlue));
     }
 
-    @Ignore
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DEFER_OUTGOING_BCASTS)
     public void testDeferOutgoingBroadcasts() throws Exception {
         final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
         setProcessFreezable(callerApp, true /* pendingFreeze */, false /* frozen */);
@@ -2350,6 +2349,8 @@
                 makeRegisteredReceiver(receiverGreenApp),
                 makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
                 makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW))));
+        // Verify that we invoke the call to freeze the caller app.
+        verify(mAms.mOomAdjuster.mCachedAppOptimizer).freezeAppAsyncImmediateLSP(callerApp);
 
         waitForIdle();
         verifyScheduleRegisteredReceiver(never(), receiverGreenApp, timeTick);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
index fcf761f..67be93b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
@@ -215,7 +215,7 @@
                 any(), any(), any(),
                 any(), any(),
                 any(), any(),
-                any(),
+                any(), any(),
                 anyLong(), anyLong());
         final ProcessRecord r = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid));
         r.setPid(myPid());
@@ -263,7 +263,7 @@
                 null, null,
                 null,
                 null, null, null,
-                null, null,
+                null, null, null,
                 0, 0);
         return app;
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/OWNERS b/services/tests/mockingservicestests/src/com/android/server/rollback/OWNERS
index daa0211..8337fd2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/rollback/OWNERS
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/OWNERS
@@ -1,3 +1 @@
-ancr@google.com
-harshitmahajan@google.com
-robertogil@google.com
+include /services/core/java/com/android/server/crashrecovery/OWNERS
\ No newline at end of file
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/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
index ef15f60..36b163e 100644
--- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
@@ -173,6 +173,25 @@
     }
 
     @Test
+    public void testGetLoggableChanges() throws Exception {
+        final long disabledChangeId = 1234L;
+        final long enabledLatestChangeId = 2345L;
+        final long enabledOlderChangeId = 3456L;
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                // Disabled changes should not be logged.
+                .addDisabledChangeWithId(disabledChangeId)
+                // A change targeting the latest sdk should be logged.
+                .addEnableSinceSdkChangeWithId(3, enabledLatestChangeId)
+                // A change targeting an old sdk should not be logged.
+                .addEnableSinceSdkChangeWithId(1, enabledOlderChangeId)
+                .build();
+
+        assertThat(compatConfig.getLoggableChanges(
+                ApplicationInfoBuilder.create().withTargetSdk(3).build()))
+                    .asList().containsExactly(enabledLatestChangeId);
+    }
+
+    @Test
     public void testPackageOverrideEnabled() throws Exception {
         CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
                 .addDisabledChangeWithId(1234L)
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
index 1dd64ff..5582e13 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
@@ -145,6 +145,7 @@
 
     @SmallTest
     @Test
+    @Ignore("b/277916462")
     public void testCompMigrationUnAffiliated_skipped() throws Exception {
         prepareAdmin1AsDo();
         prepareAdminAnotherPackageAsPo(COPE_PROFILE_USER_ID);
@@ -216,6 +217,7 @@
 
     @SmallTest
     @Test
+    @Ignore("b/277916462")
     public void testCompMigration_keepSuspendedAppsWhenDpcIsRPlus() throws Exception {
         prepareAdmin1AsDo();
         prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.R);
@@ -249,6 +251,7 @@
 
     @SmallTest
     @Test
+    @Ignore("b/277916462")
     public void testCompMigration_unsuspendAppsWhenDpcNotRPlus() throws Exception {
         prepareAdmin1AsDo();
         prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.Q);
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 26cda65..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;
@@ -6223,6 +6222,52 @@
     }
 
     @Test
+    public void testSensitiveAdjustmentsLogged() throws Exception {
+        NotificationManagerService.WorkerHandler handler = mock(
+                NotificationManagerService.WorkerHandler.class);
+        mService.setHandler(handler);
+        when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
+        when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
+
+        // Set up notifications that will be adjusted
+        final NotificationRecord r1 = spy(generateNotificationRecord(
+                mTestNotificationChannel, 1, null, true));
+        when(r1.getLifespanMs(anyLong())).thenReturn(1);
+
+        r1.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId());
+        mService.addEnqueuedNotification(r1);
+
+        // Test an adjustment for an enqueued notification
+        Bundle signals = new Bundle();
+        signals.putBoolean(Adjustment.KEY_SENSITIVE_CONTENT, true);
+        Adjustment adjustment1 = new Adjustment(
+                r1.getSbn().getPackageName(), r1.getKey(), signals, "",
+                r1.getUser().getIdentifier());
+        mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment1);
+        assertTrue(mService.checkLastSensitiveLog(false, true, 1));
+
+        // Set up notifications that will be adjusted
+        final NotificationRecord r2 = spy(generateNotificationRecord(
+                mTestNotificationChannel, 1, null, true));
+        when(r2.getLifespanMs(anyLong())).thenReturn(2);
+
+        r2.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId());
+        mService.addNotification(r2);
+        Adjustment adjustment2 = new Adjustment(
+                r2.getSbn().getPackageName(), r2.getKey(), signals, "",
+                r2.getUser().getIdentifier());
+        mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment2);
+        assertTrue(mService.checkLastSensitiveLog(true, true, 2));
+
+        signals.putBoolean(Adjustment.KEY_SENSITIVE_CONTENT, false);
+        Adjustment adjustment3 = new Adjustment(
+                r2.getSbn().getPackageName(), r2.getKey(), signals, "",
+                r2.getUser().getIdentifier());
+        mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment3);
+        assertTrue(mService.checkLastSensitiveLog(true, false, 2));
+    }
+
+    @Test
     public void testAdjustmentToImportanceNone_cancelsNotification() throws Exception {
         NotificationManagerService.WorkerHandler handler = mock(
                 NotificationManagerService.WorkerHandler.class);
@@ -13180,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/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
index 6976ec3..07d25df 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
@@ -45,6 +45,13 @@
 
     ComponentPermissionChecker permissionChecker;
 
+    private static class SensitiveLog {
+        public boolean hasPosted;
+        public boolean hasSensitiveContent;
+        public long lifetime;
+    }
+    public SensitiveLog lastSensitiveLog = null;
+
     TestableNotificationManagerService(Context context, NotificationRecordLogger logger,
             InstanceIdSequence notificationInstanceIdSequence) {
         super(context, logger, notificationInstanceIdSequence);
@@ -167,6 +174,15 @@
         return permissionChecker.check(permission, uid, owningUid, exported);
     }
 
+    @Override
+    protected void logSensitiveAdjustmentReceived(boolean hasPosted, boolean hasSensitiveContent,
+            int lifetimeMs) {
+        lastSensitiveLog = new SensitiveLog();
+        lastSensitiveLog.hasPosted = hasPosted;
+        lastSensitiveLog.hasSensitiveContent = hasSensitiveContent;
+        lastSensitiveLog.lifetime = lifetimeMs;
+    }
+
     public class StrongAuthTrackerFake extends NotificationManagerService.StrongAuthTracker {
         private int mGetStrongAuthForUserReturnValue = 0;
         StrongAuthTrackerFake(Context context) {
@@ -183,6 +199,15 @@
         }
     }
 
+    public boolean checkLastSensitiveLog(boolean hasPosted, boolean hasSensitive, int lifetime) {
+        if (lastSensitiveLog == null) {
+            return false;
+        }
+        return hasPosted == lastSensitiveLog.hasPosted
+                && hasSensitive == lastSensitiveLog.hasSensitiveContent
+                && lifetime == lastSensitiveLog.lifetime;
+    }
+
     public interface ComponentPermissionChecker {
         int check(String permission, int uid, int owningUid, boolean exported);
     }
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/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index dc504ca..a35a35a 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -153,7 +153,8 @@
             = SystemProperties.getBoolean("persist.debug.time_correction", true);
 
     private static final boolean USE_DEDICATED_HANDLER_THREAD =
-            SystemProperties.getBoolean("persist.debug.use_dedicated_handler_thread", false);
+            SystemProperties.getBoolean("persist.debug.use_dedicated_handler_thread",
+            Flags.useDedicatedHandlerThread());
 
     static final boolean DEBUG = false; // Never submit with true
     static final boolean DEBUG_RESPONSE_STATS = DEBUG || Log.isLoggable(TAG, Log.DEBUG);
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/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl b/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl
index e69b60b..c349599 100644
--- a/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl
+++ b/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl
@@ -26,5 +26,5 @@
 {
     void onQualifiedNetworkTypesChanged(int apnTypes, in int[] qualifiedNetworkTypes);
     void onNetworkValidationRequested(int networkCapability, IIntegerConsumer callback);
-    void onReconnectQualifedNetworkType(int apnTypes, int qualifiedNetworkType);
+    void onReconnectQualifiedNetworkType(int apnTypes, int qualifiedNetworkType);
 }
diff --git a/telephony/java/android/telephony/data/QualifiedNetworksService.java b/telephony/java/android/telephony/data/QualifiedNetworksService.java
index 7bfe04d..f775de6 100644
--- a/telephony/java/android/telephony/data/QualifiedNetworksService.java
+++ b/telephony/java/android/telephony/data/QualifiedNetworksService.java
@@ -238,7 +238,7 @@
                 @AccessNetworkConstants.RadioAccessNetworkType int qualifiedNetworkType) {
             if (mCallback != null) {
                 try {
-                    mCallback.onReconnectQualifedNetworkType(apnTypes, qualifiedNetworkType);
+                    mCallback.onReconnectQualifiedNetworkType(apnTypes, qualifiedNetworkType);
                 } catch (RemoteException e) {
                     loge("Failed to call onReconnectQualifiedNetworkType. " + e);
                 }
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenImagePathManager.kt b/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenImagePathManager.kt
deleted file mode 100644
index 8faf224..0000000
--- a/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenImagePathManager.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.input.screenshot
-
-import androidx.test.platform.app.InstrumentationRegistry
-import platform.test.screenshot.GoldenImagePathManager
-import platform.test.screenshot.PathConfig
-
-/** A [GoldenImagePathManager] that should be used for all Input screenshot tests. */
-class InputGoldenImagePathManager(
-        pathConfig: PathConfig,
-        assetsPathRelativeToBuildRoot: String
-) :
-        GoldenImagePathManager(
-                appContext = InstrumentationRegistry.getInstrumentation().context,
-                assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
-                deviceLocalPath =
-                    InstrumentationRegistry.getInstrumentation()
-                        .targetContext
-                        .filesDir
-                        .absolutePath
-                        .toString() + "/input_screenshots",
-                pathConfig = pathConfig,
-        ) {
-    override fun toString(): String {
-        // This string is appended to all actual/expected screenshots on the device, so make sure
-        // it is a static value.
-        return "InputGoldenImagePathManager"
-    }
-}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt b/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenPathManager.kt
similarity index 62%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt
copy to tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenPathManager.kt
index f5fba7f..9f14b136 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenPathManager.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,31 +14,28 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.screenshot.util
+package com.android.input.screenshot
 
 import androidx.test.platform.app.InstrumentationRegistry
-import platform.test.screenshot.GoldenImagePathManager
+import platform.test.screenshot.GoldenPathManager
 import platform.test.screenshot.PathConfig
 
-/** A [GoldenImagePathManager] that should be used for all Settings screenshot tests. */
-class SettingsGoldenImagePathManager(
-    pathConfig: PathConfig,
-    assetsPathRelativeToBuildRoot: String
-) :
-    GoldenImagePathManager(
+/** A [GoldenPathManager] that should be used for all Input screenshot tests. */
+class InputGoldenPathManager(pathConfig: PathConfig, assetsPathRelativeToBuildRoot: String) :
+    GoldenPathManager(
         appContext = InstrumentationRegistry.getInstrumentation().context,
         assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
         deviceLocalPath =
-        InstrumentationRegistry.getInstrumentation()
-            .targetContext
-            .filesDir
-            .absolutePath
-            .toString() + "/settings_screenshots",
+            InstrumentationRegistry.getInstrumentation()
+                .targetContext
+                .filesDir
+                .absolutePath
+                .toString() + "/input_screenshots",
         pathConfig = pathConfig,
     ) {
     override fun toString(): String {
         // This string is appended to all actual/expected screenshots on the device, so make sure
         // it is a static value.
-        return "SettingsGoldenImagePathManager"
+        return "InputGoldenPathManager"
     }
 }
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
index 75dab41..2f40896 100644
--- a/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
@@ -44,7 +44,7 @@
     private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
     private val screenshotRule =
         ScreenshotTestRule(
-            InputGoldenImagePathManager(
+            InputGoldenPathManager(
                 getEmulatedDevicePathConfig(emulationSpec),
                 assetsPathRelativeToBuildRoot
             )
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 755636a..75284c7 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -43,13 +43,13 @@
 import android.os.test.TestLooper;
 import android.provider.DeviceConfig;
 import android.util.AtomicFile;
-import android.util.LongArrayQueue;
 import android.util.Xml;
+import android.utils.LongArrayQueue;
+import android.utils.XmlUtils;
 
 import androidx.test.InstrumentationRegistry;
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.internal.util.XmlUtils;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.PackageWatchdog.HealthCheckState;