Merge "Remove obsolete classes ActionProxyReceiver and DeleteScreenshotReceiver" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index 7552b5c0..2c2a759 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -6849,7 +6849,7 @@
   }
 
   public abstract static class Notification.Style {
-    ctor public Notification.Style();
+    ctor @Deprecated public Notification.Style();
     method public android.app.Notification build();
     method protected void checkBuilder();
     method protected android.widget.RemoteViews getStandardView(int);
@@ -12487,33 +12487,33 @@
   }
 
   public class LauncherApps {
-    method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(String, android.os.UserHandle);
-    method @NonNull public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getAllPackageInstallerSessions();
+    method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(String, android.os.UserHandle);
+    method @NonNull @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getAllPackageInstallerSessions();
     method @FlaggedApi("android.os.allow_private_profile") @Nullable @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public android.content.IntentSender getAppMarketActivityIntent(@Nullable String, @NonNull android.os.UserHandle);
-    method public android.content.pm.ApplicationInfo getApplicationInfo(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public android.content.pm.ApplicationInfo getApplicationInfo(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
     method @FlaggedApi("android.os.allow_private_profile") @Nullable @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public final android.content.pm.LauncherUserInfo getLauncherUserInfo(@NonNull android.os.UserHandle);
     method public android.content.pm.LauncherApps.PinItemRequest getPinItemRequest(android.content.Intent);
     method @FlaggedApi("android.os.allow_private_profile") @NonNull @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public java.util.List<java.lang.String> getPreInstalledSystemPackages(@NonNull android.os.UserHandle);
-    method public java.util.List<android.os.UserHandle> getProfiles();
+    method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public java.util.List<android.os.UserHandle> getProfiles();
     method public android.graphics.drawable.Drawable getShortcutBadgedIconDrawable(android.content.pm.ShortcutInfo, int);
     method @Nullable public android.content.IntentSender getShortcutConfigActivityIntent(@NonNull android.content.pm.LauncherActivityInfo);
     method public java.util.List<android.content.pm.LauncherActivityInfo> getShortcutConfigActivityList(@Nullable String, @NonNull android.os.UserHandle);
     method public android.graphics.drawable.Drawable getShortcutIconDrawable(@NonNull android.content.pm.ShortcutInfo, int);
     method @Nullable public android.app.PendingIntent getShortcutIntent(@NonNull String, @NonNull String, @Nullable android.os.Bundle, @NonNull android.os.UserHandle);
     method @Nullable public java.util.List<android.content.pm.ShortcutInfo> getShortcuts(@NonNull android.content.pm.LauncherApps.ShortcutQuery, @NonNull android.os.UserHandle);
-    method @Nullable public android.os.Bundle getSuspendedPackageLauncherExtras(String, android.os.UserHandle);
+    method @Nullable @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public android.os.Bundle getSuspendedPackageLauncherExtras(String, android.os.UserHandle);
     method public boolean hasShortcutHostPermission();
-    method public boolean isActivityEnabled(android.content.ComponentName, android.os.UserHandle);
-    method public boolean isPackageEnabled(String, android.os.UserHandle);
+    method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public boolean isActivityEnabled(android.content.ComponentName, android.os.UserHandle);
+    method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public boolean isPackageEnabled(String, android.os.UserHandle);
     method public void pinShortcuts(@NonNull String, @NonNull java.util.List<java.lang.String>, @NonNull android.os.UserHandle);
-    method public void registerCallback(android.content.pm.LauncherApps.Callback);
-    method public void registerCallback(android.content.pm.LauncherApps.Callback, android.os.Handler);
+    method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public void registerCallback(android.content.pm.LauncherApps.Callback);
+    method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public void registerCallback(android.content.pm.LauncherApps.Callback, android.os.Handler);
     method public void registerPackageInstallerSessionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.content.pm.PackageInstaller.SessionCallback);
-    method public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle);
+    method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle);
     method @FlaggedApi("android.content.pm.archiving") public void setArchiveCompatibility(@NonNull android.content.pm.LauncherApps.ArchiveCompatibilityParams);
-    method public boolean shouldHideFromSuggestions(@NonNull String, @NonNull android.os.UserHandle);
-    method public void startAppDetailsActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
-    method public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
+    method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public boolean shouldHideFromSuggestions(@NonNull String, @NonNull android.os.UserHandle);
+    method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public void startAppDetailsActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
+    method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
     method public void startPackageInstallerSessionDetailsActivity(@NonNull android.content.pm.PackageInstaller.SessionInfo, @Nullable android.graphics.Rect, @Nullable android.os.Bundle);
     method public void startShortcut(@NonNull String, @NonNull String, @Nullable android.graphics.Rect, @Nullable android.os.Bundle, @NonNull android.os.UserHandle);
     method public void startShortcut(@NonNull android.content.pm.ShortcutInfo, @Nullable android.graphics.Rect, @Nullable android.os.Bundle);
@@ -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 {
@@ -52193,7 +52193,7 @@
     method @FlaggedApi("com.android.window.flags.sdk_desired_present_time") @NonNull public android.view.SurfaceControl.Transaction addTransactionCompletedListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.SurfaceControl.TransactionStats>);
     method public void apply();
     method @NonNull public android.view.SurfaceControl.Transaction clearFrameRate(@NonNull android.view.SurfaceControl);
-    method @NonNull public android.view.SurfaceControl.Transaction clearTrustedPresentationCallback(@NonNull android.view.SurfaceControl);
+    method @Deprecated @NonNull public android.view.SurfaceControl.Transaction clearTrustedPresentationCallback(@NonNull android.view.SurfaceControl);
     method public void close();
     method public int describeContents();
     method @NonNull public android.view.SurfaceControl.Transaction merge(@NonNull android.view.SurfaceControl.Transaction);
@@ -52218,7 +52218,7 @@
     method @NonNull public android.view.SurfaceControl.Transaction setOpaque(@NonNull android.view.SurfaceControl, boolean);
     method @NonNull public android.view.SurfaceControl.Transaction setPosition(@NonNull android.view.SurfaceControl, float, float);
     method @NonNull public android.view.SurfaceControl.Transaction setScale(@NonNull android.view.SurfaceControl, float, float);
-    method @NonNull public android.view.SurfaceControl.Transaction setTrustedPresentationCallback(@NonNull android.view.SurfaceControl, @NonNull android.view.SurfaceControl.TrustedPresentationThresholds, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method @Deprecated @NonNull public android.view.SurfaceControl.Transaction setTrustedPresentationCallback(@NonNull android.view.SurfaceControl, @NonNull android.view.SurfaceControl.TrustedPresentationThresholds, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @NonNull public android.view.SurfaceControl.Transaction setVisibility(@NonNull android.view.SurfaceControl, boolean);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.view.SurfaceControl.Transaction> CREATOR;
@@ -52233,8 +52233,8 @@
     method @FlaggedApi("com.android.window.flags.sdk_desired_present_time") @NonNull public android.hardware.SyncFence getPresentFence();
   }
 
-  public static final class SurfaceControl.TrustedPresentationThresholds {
-    ctor public SurfaceControl.TrustedPresentationThresholds(@FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @IntRange(from=1) int);
+  @Deprecated public static final class SurfaceControl.TrustedPresentationThresholds {
+    ctor @Deprecated public SurfaceControl.TrustedPresentationThresholds(@FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @IntRange(from=1) int);
   }
 
   @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public interface SurfaceControlInputReceiver {
@@ -52249,7 +52249,7 @@
     method public void relayout(int, int);
     method public void release();
     method public void setView(@NonNull android.view.View, int, int);
-    method public boolean transferTouchGestureToHost();
+    method @Deprecated public boolean transferTouchGestureToHost();
   }
 
   public static final class SurfaceControlViewHost.SurfacePackage implements android.os.Parcelable {
@@ -52308,7 +52308,7 @@
     ctor public SurfaceView(android.content.Context, android.util.AttributeSet, int, int);
     method public void applyTransactionToFrame(@NonNull android.view.SurfaceControl.Transaction);
     method public android.view.SurfaceHolder getHolder();
-    method @Nullable public android.os.IBinder getHostToken();
+    method @Deprecated @Nullable public android.os.IBinder getHostToken();
     method public android.view.SurfaceControl getSurfaceControl();
     method public void setChildSurfacePackage(@NonNull android.view.SurfaceControlViewHost.SurfacePackage);
     method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public void setDesiredHdrHeadroom(@FloatRange(from=0.0f, to=10000.0) float);
@@ -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 4b04d10..7aeecbf 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -198,7 +198,6 @@
     field public static final String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
     field public static final String MANAGE_DEVICE_POLICY_APP_EXEMPTIONS = "android.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS";
     field @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") public static final String MANAGE_DEVICE_POLICY_AUDIT_LOGGING = "android.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING";
-    field @FlaggedApi("android.app.admin.flags.device_theft_api_enabled") public static final String MANAGE_DEVICE_POLICY_THEFT_DETECTION = "android.permission.MANAGE_DEVICE_POLICY_THEFT_DETECTION";
     field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String MANAGE_ENHANCED_CONFIRMATION_STATES = "android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES";
     field public static final String MANAGE_ETHERNET_NETWORKS = "android.permission.MANAGE_ETHERNET_NETWORKS";
     field public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission.MANAGE_FACTORY_RESET_PROTECTION";
@@ -282,6 +281,7 @@
     field @FlaggedApi("android.content.pm.quarantined_enabled") public static final String QUARANTINE_APPS = "android.permission.QUARANTINE_APPS";
     field public static final String QUERY_ADMIN_POLICY = "android.permission.QUERY_ADMIN_POLICY";
     field public static final String QUERY_CLONED_APPS = "android.permission.QUERY_CLONED_APPS";
+    field @FlaggedApi("android.app.admin.flags.device_theft_api_enabled") public static final String QUERY_DEVICE_STOLEN_STATE = "android.permission.QUERY_DEVICE_STOLEN_STATE";
     field @Deprecated public static final String QUERY_TIME_ZONE_RULES = "android.permission.QUERY_TIME_ZONE_RULES";
     field public static final String QUERY_USERS = "android.permission.QUERY_USERS";
     field public static final String RADIO_SCAN_WITHOUT_LOCATION = "android.permission.RADIO_SCAN_WITHOUT_LOCATION";
@@ -1328,12 +1328,12 @@
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public int getUserProvisioningState();
     method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public boolean isAuditLogEnabled();
     method public boolean isDeviceManaged();
+    method @FlaggedApi("android.app.admin.flags.device_theft_api_enabled") @RequiresPermission(android.Manifest.permission.QUERY_DEVICE_STOLEN_STATE) public boolean isDevicePotentiallyStolen();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioned();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioningConfigApplied();
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean isDpcDownloaded();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public boolean isManagedKiosk();
     method public boolean isSecondaryLockscreenEnabled(@NonNull android.os.UserHandle);
-    method @FlaggedApi("android.app.admin.flags.device_theft_api_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_THEFT_DETECTION) public boolean isTheftDetectionTriggered();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public boolean isUnattendedManagedKiosk();
     method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long);
     method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long, boolean);
@@ -2190,14 +2190,6 @@
 
 package android.app.ondeviceintelligence {
 
-  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class Content implements android.os.Parcelable {
-    ctor public Content(@NonNull android.os.Bundle);
-    method public int describeContents();
-    method @NonNull public android.os.Bundle getData();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.Content> CREATOR;
-  }
-
   @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface DownloadCallback {
     method public void onDownloadCompleted(@NonNull android.os.PersistableBundle);
     method public void onDownloadFailed(int, @Nullable String, @NonNull android.os.PersistableBundle);
@@ -2233,11 +2225,11 @@
   }
 
   @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class FeatureDetails implements android.os.Parcelable {
-    ctor public FeatureDetails(@android.app.ondeviceintelligence.FeatureDetails.Status int, @NonNull android.os.PersistableBundle);
-    ctor public FeatureDetails(@android.app.ondeviceintelligence.FeatureDetails.Status int);
+    ctor public FeatureDetails(int, @NonNull android.os.PersistableBundle);
+    ctor public FeatureDetails(int);
     method public int describeContents();
     method @NonNull public android.os.PersistableBundle getFeatureDetailParams();
-    method @android.app.ondeviceintelligence.FeatureDetails.Status public int getFeatureStatus();
+    method public int getFeatureStatus();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.FeatureDetails> CREATOR;
     field public static final int FEATURE_STATUS_AVAILABLE = 3; // 0x3
@@ -2247,35 +2239,14 @@
     field public static final int FEATURE_STATUS_UNAVAILABLE = 0; // 0x0
   }
 
-  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE_USE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD}) public static @interface FeatureDetails.Status {
-  }
-
-  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public class OnDeviceIntelligenceManager {
-    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeature(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
-    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeatureDetails(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
-    method @Nullable @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public String getRemoteServicePackageName();
-    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getVersion(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.LongConsumer);
-    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void listFeatures(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
-    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequest(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.ProcessingOutcomeReceiver);
-    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver);
-    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestFeatureDownload(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.DownloadCallback);
-    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestTokenInfo(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
-    field public static final int REQUEST_TYPE_EMBEDDINGS = 2; // 0x2
-    field public static final int REQUEST_TYPE_INFERENCE = 0; // 0x0
-    field public static final int REQUEST_TYPE_PREPARE = 1; // 0x1
-  }
-
-  public static class OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException extends java.lang.Exception {
-    ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException(int, @NonNull String, @NonNull android.os.PersistableBundle);
-    ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException(int, @NonNull android.os.PersistableBundle);
+  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public class OnDeviceIntelligenceException extends java.lang.Exception {
+    ctor public OnDeviceIntelligenceException(int, @NonNull String, @NonNull android.os.PersistableBundle);
+    ctor public OnDeviceIntelligenceException(int, @NonNull android.os.PersistableBundle);
+    ctor public OnDeviceIntelligenceException(int, @NonNull String);
+    ctor public OnDeviceIntelligenceException(int);
     method public int getErrorCode();
     method @NonNull public android.os.PersistableBundle getErrorParams();
-    field public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 1000; // 0x3e8
-  }
-
-  public static class OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException extends android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException {
-    ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException(int, @NonNull String, @NonNull android.os.PersistableBundle);
-    ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException(int, @NonNull android.os.PersistableBundle);
+    field public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 100; // 0x64
     field public static final int PROCESSING_ERROR_BAD_DATA = 2; // 0x2
     field public static final int PROCESSING_ERROR_BAD_REQUEST = 3; // 0x3
     field public static final int PROCESSING_ERROR_BUSY = 9; // 0x9
@@ -2291,10 +2262,28 @@
     field public static final int PROCESSING_ERROR_SERVICE_UNAVAILABLE = 15; // 0xf
     field public static final int PROCESSING_ERROR_SUSPENDED = 13; // 0xd
     field public static final int PROCESSING_ERROR_UNKNOWN = 1; // 0x1
+    field public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 200; // 0xc8
   }
 
-  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface ProcessingOutcomeReceiver extends android.os.OutcomeReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> {
-    method public default void onDataAugmentRequest(@NonNull android.app.ondeviceintelligence.Content, @NonNull java.util.function.Consumer<android.app.ondeviceintelligence.Content>);
+  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class OnDeviceIntelligenceManager {
+    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeature(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeatureDetails(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+    method @Nullable @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public String getRemoteServicePackageName();
+    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getVersion(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.LongConsumer);
+    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void listFeatures(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequest(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.ProcessingCallback);
+    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.StreamingProcessingCallback);
+    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestFeatureDownload(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.DownloadCallback);
+    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestTokenInfo(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+    field public static final int REQUEST_TYPE_EMBEDDINGS = 2; // 0x2
+    field public static final int REQUEST_TYPE_INFERENCE = 0; // 0x0
+    field public static final int REQUEST_TYPE_PREPARE = 1; // 0x1
+  }
+
+  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface ProcessingCallback {
+    method public default void onDataAugmentRequest(@NonNull android.os.Bundle, @NonNull java.util.function.Consumer<android.os.Bundle>);
+    method public void onError(@NonNull android.app.ondeviceintelligence.OnDeviceIntelligenceException);
+    method public void onResult(@NonNull android.os.Bundle);
   }
 
   @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class ProcessingSignal {
@@ -2307,8 +2296,8 @@
     method public void onSignalReceived(@NonNull android.os.PersistableBundle);
   }
 
-  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface StreamedProcessingOutcomeReceiver extends android.app.ondeviceintelligence.ProcessingOutcomeReceiver {
-    method public void onNewContent(@NonNull android.app.ondeviceintelligence.Content);
+  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface StreamingProcessingCallback extends android.app.ondeviceintelligence.ProcessingCallback {
+    method public void onPartialResult(@NonNull android.os.Bundle);
   }
 
   @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class TokenInfo implements android.os.Parcelable {
@@ -3338,7 +3327,7 @@
     method @FlaggedApi("android.app.wearable.enable_data_request_observer_api") @Nullable public static android.app.wearable.WearableSensingDataRequest getDataRequestFromIntent(@NonNull android.content.Intent);
     method @FlaggedApi("android.app.wearable.enable_provide_wearable_connection_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideConnection(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideData(@NonNull android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideDataStream(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideDataStream(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @FlaggedApi("android.app.wearable.enable_data_request_observer_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void registerDataRequestObserver(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @FlaggedApi("android.app.wearable.enable_hotword_wearable_sensing_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void startHotwordRecognition(@Nullable android.content.ComponentName, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @FlaggedApi("android.app.wearable.enable_hotword_wearable_sensing_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void stopHotwordRecognition(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
@@ -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
@@ -5017,7 +5006,7 @@
   }
 
   public static interface DeviceStateManager.DeviceStateCallback {
-    method public default void onDeviceStateChanged(@NonNull android.hardware.devicestate.DeviceState);
+    method public void onDeviceStateChanged(@NonNull android.hardware.devicestate.DeviceState);
     method public default void onSupportedStatesChanged(@NonNull java.util.List<android.hardware.devicestate.DeviceState>);
   }
 
@@ -6203,8 +6192,6 @@
     method public long getNanoAppId();
     method public boolean isBroadcastMessage();
     method @FlaggedApi("android.chre.flags.reliable_message") public boolean isReliable();
-    method @FlaggedApi("android.chre.flags.reliable_message") public void setIsReliable(boolean);
-    method @FlaggedApi("android.chre.flags.reliable_message") public void setMessageSequenceNumber(int);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.location.NanoAppMessage> CREATOR;
   }
@@ -12956,38 +12943,26 @@
     ctor public OnDeviceIntelligenceService();
     method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
     method public abstract void onDownloadFeature(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull android.app.ondeviceintelligence.DownloadCallback);
-    method public abstract void onGetFeature(int, int, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
-    method public abstract void onGetFeatureDetails(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+    method public abstract void onGetFeature(int, int, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+    method public abstract void onGetFeatureDetails(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
     method public abstract void onGetReadOnlyFeatureFileDescriptorMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,android.os.ParcelFileDescriptor>>);
     method public abstract void onGetVersion(@NonNull java.util.function.LongConsumer);
     method public abstract void onInferenceServiceConnected();
     method public abstract void onInferenceServiceDisconnected();
-    method public abstract void onListFeatures(int, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
-    method public final void updateProcessingState(@NonNull android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException>);
+    method public abstract void onListFeatures(int, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+    method public final void updateProcessingState(@NonNull android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
     field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceIntelligenceService";
   }
 
-  public abstract static class OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException extends java.lang.Exception {
-    ctor public OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException(int);
-    ctor public OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException(int, @NonNull String);
-    method public int getErrorCode();
-  }
-
-  public static class OnDeviceIntelligenceService.OnDeviceUpdateProcessingException extends android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException {
-    ctor public OnDeviceIntelligenceService.OnDeviceUpdateProcessingException(int);
-    ctor public OnDeviceIntelligenceService.OnDeviceUpdateProcessingException(int, @NonNull String);
-    field public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 1; // 0x1
-  }
-
   @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceSandboxedInferenceService extends android.app.Service {
     ctor public OnDeviceSandboxedInferenceService();
     method public final void fetchFeatureFileInputStreamMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.io.FileInputStream>>);
     method @NonNull public java.util.concurrent.Executor getCallbackExecutor();
     method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
-    method @NonNull public abstract void onProcessRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.ProcessingOutcomeReceiver);
-    method @NonNull public abstract void onProcessRequestStreaming(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver);
-    method @NonNull public abstract void onTokenInfoRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
-    method public abstract void onUpdateProcessingState(@NonNull android.os.Bundle, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException>);
+    method @NonNull public abstract void onProcessRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.ProcessingCallback);
+    method @NonNull public abstract void onProcessRequestStreaming(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.StreamingProcessingCallback);
+    method @NonNull public abstract void onTokenInfoRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, @Nullable android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+    method public abstract void onUpdateProcessingState(@NonNull android.os.Bundle, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
     method public final java.io.FileInputStream openFileInput(@NonNull String) throws java.io.FileNotFoundException;
     method public final void openFileInputAsync(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.io.FileInputStream>) throws java.io.FileNotFoundException;
     field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService";
@@ -13961,9 +13936,8 @@
   }
 
   @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final class DisconnectCause.Builder {
-    ctor public DisconnectCause.Builder();
+    ctor public DisconnectCause.Builder(int);
     method @NonNull public android.telecom.DisconnectCause build();
-    method @NonNull public android.telecom.DisconnectCause.Builder setCode(int);
     method @NonNull public android.telecom.DisconnectCause.Builder setDescription(@Nullable CharSequence);
     method @NonNull public android.telecom.DisconnectCause.Builder setImsReasonInfo(@Nullable android.telephony.ims.ImsReasonInfo);
     method @NonNull public android.telecom.DisconnectCause.Builder setLabel(@Nullable CharSequence);
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..c1af719 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";
@@ -1624,25 +1625,37 @@
 package android.hardware.devicestate {
 
   @FlaggedApi("android.hardware.devicestate.feature.flags.device_state_property_api") public final class DeviceState {
+    ctor public DeviceState(@NonNull android.hardware.devicestate.DeviceState.Configuration);
     field public static final int PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST = 8; // 0x8
   }
 
+  public static final class DeviceState.Configuration implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getIdentifier();
+    method @NonNull public String getName();
+    method @NonNull public java.util.Set<java.lang.Integer> getPhysicalProperties();
+    method @NonNull public java.util.Set<java.lang.Integer> getSystemProperties();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.devicestate.DeviceState.Configuration> CREATOR;
+  }
+
+  public static final class DeviceState.Configuration.Builder {
+    ctor public DeviceState.Configuration.Builder(int, @NonNull String);
+    method @NonNull public android.hardware.devicestate.DeviceState.Configuration build();
+    method @NonNull public android.hardware.devicestate.DeviceState.Configuration.Builder setPhysicalProperties(@NonNull java.util.Set<java.lang.Integer>);
+    method @NonNull public android.hardware.devicestate.DeviceState.Configuration.Builder setSystemProperties(@NonNull java.util.Set<java.lang.Integer>);
+  }
+
   @FlaggedApi("android.hardware.devicestate.feature.flags.device_state_property_api") public final class DeviceStateManager {
     method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void cancelBaseStateOverride();
     method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void cancelStateRequest();
-    method @Deprecated @NonNull public int[] getSupportedStates();
     method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void requestBaseStateOverride(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback);
     method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void requestState(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback);
+    field public static final int INVALID_DEVICE_STATE_IDENTIFIER = -1; // 0xffffffff
     field public static final int MAXIMUM_DEVICE_STATE_IDENTIFIER = 10000; // 0x2710
     field public static final int MINIMUM_DEVICE_STATE_IDENTIFIER = 0; // 0x0
   }
 
-  public static interface DeviceStateManager.DeviceStateCallback {
-    method @Deprecated public default void onBaseStateChanged(int);
-    method @Deprecated public void onStateChanged(int);
-    method @Deprecated public default void onSupportedStatesChanged(@NonNull int[]);
-  }
-
   public final class DeviceStateRequest {
     method public int getFlags();
     method public int getState();
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 2afc78c..a8352fa 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2951,7 +2951,9 @@
         new AppOpInfo.Builder(OP_ESTABLISH_VPN_MANAGER, OPSTR_ESTABLISH_VPN_MANAGER,
                 "ESTABLISH_VPN_MANAGER").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
         new AppOpInfo.Builder(OP_ACCESS_RESTRICTED_SETTINGS, OPSTR_ACCESS_RESTRICTED_SETTINGS,
-                "ACCESS_RESTRICTED_SETTINGS").setDefaultMode(AppOpsManager.MODE_ALLOWED)
+                "ACCESS_RESTRICTED_SETTINGS").setDefaultMode(
+                        android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()
+                                ? MODE_DEFAULT : MODE_ALLOWED)
             .setDisableReset(true).setRestrictRead(true).build(),
         new AppOpInfo.Builder(OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO,
                 "RECEIVE_SOUNDTRIGGER_AUDIO").setDefaultMode(AppOpsManager.MODE_ALLOWED)
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index ed0c933..df566db 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -272,17 +272,10 @@
 
     @UnsupportedAppUsage
     private Context mOuterContext;
-
-    private final Object mThemeLock = new Object();
-
     @UnsupportedAppUsage
-    @GuardedBy("mThemeLock")
     private int mThemeResource = 0;
-
     @UnsupportedAppUsage
-    @GuardedBy("mThemeLock")
     private Resources.Theme mTheme = null;
-
     @UnsupportedAppUsage
     private PackageManager mPackageManager;
     private Context mReceiverRestrictedContext = null;
@@ -295,6 +288,7 @@
 
     private ContentCaptureOptions mContentCaptureOptions = null;
 
+    private final Object mSync = new Object();
     /**
      * Indicates this {@link Context} can not handle UI components properly and is not associated
      * with a {@link Display} instance.
@@ -346,18 +340,21 @@
      */
     private boolean mOwnsToken = false;
 
-    private final Object mDirsLock = new Object();
-    private volatile File mDatabasesDir;
-    @UnsupportedAppUsage private volatile File mPreferencesDir;
-    private volatile File mFilesDir;
-    private volatile File mCratesDir;
-    private volatile File mNoBackupFilesDir;
-    private volatile File[] mExternalFilesDirs;
-    private volatile File[] mObbDirs;
-    private volatile File mCacheDir;
-    private volatile File mCodeCacheDir;
-    private volatile File[] mExternalCacheDirs;
-    private volatile File[] mExternalMediaDirs;
+    @GuardedBy("mSync")
+    private File mDatabasesDir;
+    @GuardedBy("mSync")
+    @UnsupportedAppUsage
+    private File mPreferencesDir;
+    @GuardedBy("mSync")
+    private File mFilesDir;
+    @GuardedBy("mSync")
+    private File mCratesDir;
+    @GuardedBy("mSync")
+    private File mNoBackupFilesDir;
+    @GuardedBy("mSync")
+    private File mCacheDir;
+    @GuardedBy("mSync")
+    private File mCodeCacheDir;
 
     // The system service cache for the system services that are cached per-ContextImpl.
     @UnsupportedAppUsage
@@ -461,7 +458,7 @@
 
     @Override
     public void setTheme(int resId) {
-        synchronized (mThemeLock) {
+        synchronized (mSync) {
             if (mThemeResource != resId) {
                 mThemeResource = resId;
                 initializeTheme();
@@ -471,14 +468,14 @@
 
     @Override
     public int getThemeResId() {
-        synchronized (mThemeLock) {
+        synchronized (mSync) {
             return mThemeResource;
         }
     }
 
     @Override
     public Resources.Theme getTheme() {
-        synchronized (mThemeLock) {
+        synchronized (mSync) {
             if (mTheme != null) {
                 return mTheme;
             }
@@ -491,7 +488,6 @@
         }
     }
 
-    @GuardedBy("mThemeLock")
     private void initializeTheme() {
         if (mTheme == null) {
             mTheme = mResources.newTheme();
@@ -741,18 +737,12 @@
 
     @UnsupportedAppUsage
     private File getPreferencesDir() {
-        File localPreferencesDir = mPreferencesDir;
-        if (localPreferencesDir == null) {
-            synchronized (mDirsLock) {
-                localPreferencesDir = mPreferencesDir;
-                if (localPreferencesDir == null) {
-                    localPreferencesDir = new File(getDataDir(), "shared_prefs");
-                    ensurePrivateDirExists(localPreferencesDir);
-                    mPreferencesDir = localPreferencesDir;
-                }
+        synchronized (mSync) {
+            if (mPreferencesDir == null) {
+                mPreferencesDir = new File(getDataDir(), "shared_prefs");
             }
+            return ensurePrivateDirExists(mPreferencesDir);
         }
-        return localPreferencesDir;
     }
 
     @Override
@@ -794,16 +784,16 @@
     /**
      * Common-path handling of app data dir creation
      */
-    private static void ensurePrivateDirExists(File file) {
-        ensurePrivateDirExists(file, 0771, -1, null);
+    private static File ensurePrivateDirExists(File file) {
+        return ensurePrivateDirExists(file, 0771, -1, null);
     }
 
-    private static void ensurePrivateCacheDirExists(File file, String xattr) {
+    private static File ensurePrivateCacheDirExists(File file, String xattr) {
         final int gid = UserHandle.getCacheAppGid(Process.myUid());
-        ensurePrivateDirExists(file, 02771, gid, xattr);
+        return ensurePrivateDirExists(file, 02771, gid, xattr);
     }
 
-    private static void ensurePrivateDirExists(File file, int mode, int gid, String xattr) {
+    private static File ensurePrivateDirExists(File file, int mode, int gid, String xattr) {
         if (!file.exists()) {
             final String path = file.getAbsolutePath();
             try {
@@ -831,22 +821,17 @@
                 }
             }
         }
+        return file;
     }
 
     @Override
     public File getFilesDir() {
-        File localFilesDir = mFilesDir;
-        if (localFilesDir == null) {
-            localFilesDir = mFilesDir;
-            synchronized (mDirsLock) {
-                if (localFilesDir == null) {
-                    localFilesDir = new File(getDataDir(), "files");
-                    ensurePrivateDirExists(localFilesDir);
-                    mFilesDir = localFilesDir;
-                }
+        synchronized (mSync) {
+            if (mFilesDir == null) {
+                mFilesDir = new File(getDataDir(), "files");
             }
+            return ensurePrivateDirExists(mFilesDir);
         }
-        return localFilesDir;
     }
 
     @Override
@@ -856,37 +841,25 @@
         final Path absoluteNormalizedCratePath = cratesRootPath.resolve(crateId)
                 .toAbsolutePath().normalize();
 
-        File localCratesDir = mCratesDir;
-        if (localCratesDir == null) {
-            synchronized (mDirsLock) {
-                localCratesDir = mCratesDir;
-                if (localCratesDir == null) {
-                    localCratesDir = cratesRootPath.toFile();
-                    ensurePrivateDirExists(localCratesDir);
-                    mCratesDir = localCratesDir;
-                }
+        synchronized (mSync) {
+            if (mCratesDir == null) {
+                mCratesDir = cratesRootPath.toFile();
             }
+            ensurePrivateDirExists(mCratesDir);
         }
 
-        File crateDir = absoluteNormalizedCratePath.toFile();
-        ensurePrivateDirExists(crateDir);
-        return crateDir;
+        File cratedDir = absoluteNormalizedCratePath.toFile();
+        return ensurePrivateDirExists(cratedDir);
     }
 
     @Override
     public File getNoBackupFilesDir() {
-        File localNoBackupFilesDir = mNoBackupFilesDir;
-        if (localNoBackupFilesDir == null) {
-            synchronized (mDirsLock) {
-                localNoBackupFilesDir = mNoBackupFilesDir;
-                if (localNoBackupFilesDir == null) {
-                    localNoBackupFilesDir = new File(getDataDir(), "no_backup");
-                    ensurePrivateDirExists(localNoBackupFilesDir);
-                    mNoBackupFilesDir = localNoBackupFilesDir;
-                }
+        synchronized (mSync) {
+            if (mNoBackupFilesDir == null) {
+                mNoBackupFilesDir = new File(getDataDir(), "no_backup");
             }
+            return ensurePrivateDirExists(mNoBackupFilesDir);
         }
-        return localNoBackupFilesDir;
     }
 
     @Override
@@ -898,24 +871,13 @@
 
     @Override
     public File[] getExternalFilesDirs(String type) {
-        File[] localExternalFilesDirs = mExternalFilesDirs;
-        if (localExternalFilesDirs == null) {
-            synchronized (mDirsLock) {
-                localExternalFilesDirs = mExternalFilesDirs;
-                if (localExternalFilesDirs == null) {
-                    localExternalFilesDirs =
-                            Environment.buildExternalStorageAppFilesDirs(getPackageName());
-                    if (type != null) {
-                        localExternalFilesDirs =
-                                Environment.buildPaths(localExternalFilesDirs, type);
-                    }
-                    localExternalFilesDirs = ensureExternalDirsExistOrFilter(
-                            localExternalFilesDirs, true /* tryCreateInProcess */);
-                    mExternalFilesDirs = localExternalFilesDirs;
-                }
+        synchronized (mSync) {
+            File[] dirs = Environment.buildExternalStorageAppFilesDirs(getPackageName());
+            if (type != null) {
+                dirs = Environment.buildPaths(dirs, type);
             }
+            return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */);
         }
-        return localExternalFilesDirs;
     }
 
     @Override
@@ -927,51 +889,30 @@
 
     @Override
     public File[] getObbDirs() {
-        File[] localObbDirs = mObbDirs;
-        if (mObbDirs == null) {
-            synchronized (mDirsLock) {
-                localObbDirs = mObbDirs;
-                if (localObbDirs == null) {
-                    localObbDirs = Environment.buildExternalStorageAppObbDirs(getPackageName());
-                    localObbDirs = ensureExternalDirsExistOrFilter(
-                            localObbDirs, true /* tryCreateInProcess */);
-                    mObbDirs = localObbDirs;
-                }
-            }
+        synchronized (mSync) {
+            File[] dirs = Environment.buildExternalStorageAppObbDirs(getPackageName());
+            return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */);
         }
-        return localObbDirs;
     }
 
     @Override
     public File getCacheDir() {
-        File localCacheDir = mCacheDir;
-        if (localCacheDir == null) {
-            synchronized (mDirsLock) {
-                localCacheDir = mCacheDir;
-                if (localCacheDir == null) {
-                    localCacheDir = new File(getDataDir(), "cache");
-                    ensurePrivateCacheDirExists(localCacheDir, XATTR_INODE_CACHE);
-                    mCacheDir = localCacheDir;
-                }
+        synchronized (mSync) {
+            if (mCacheDir == null) {
+                mCacheDir = new File(getDataDir(), "cache");
             }
+            return ensurePrivateCacheDirExists(mCacheDir, XATTR_INODE_CACHE);
         }
-        return localCacheDir;
     }
 
     @Override
     public File getCodeCacheDir() {
-        File localCodeCacheDir = mCodeCacheDir;
-        if (localCodeCacheDir == null) {
-            synchronized (mDirsLock) {
-                localCodeCacheDir = mCodeCacheDir;
-                if (localCodeCacheDir == null) {
-                    localCodeCacheDir = getCodeCacheDirBeforeBind(getDataDir());
-                    ensurePrivateCacheDirExists(localCodeCacheDir, XATTR_INODE_CODE_CACHE);
-                    mCodeCacheDir = localCodeCacheDir;
-                }
+        synchronized (mSync) {
+            if (mCodeCacheDir == null) {
+                mCodeCacheDir = getCodeCacheDirBeforeBind(getDataDir());
             }
+            return ensurePrivateCacheDirExists(mCodeCacheDir, XATTR_INODE_CODE_CACHE);
         }
-        return localCodeCacheDir;
     }
 
     /**
@@ -992,37 +933,21 @@
 
     @Override
     public File[] getExternalCacheDirs() {
-        File[] localExternalCacheDirs = mExternalCacheDirs;
-        if (localExternalCacheDirs == null) {
-            synchronized (mDirsLock) {
-                localExternalCacheDirs = mExternalCacheDirs;
-                if (localExternalCacheDirs == null) {
-                    localExternalCacheDirs =
-                            Environment.buildExternalStorageAppCacheDirs(getPackageName());
-                    localExternalCacheDirs = ensureExternalDirsExistOrFilter(
-                            localExternalCacheDirs, false /* tryCreateInProcess */);
-                    mExternalCacheDirs = localExternalCacheDirs;
-                }
-            }
+        synchronized (mSync) {
+            File[] dirs = Environment.buildExternalStorageAppCacheDirs(getPackageName());
+            // We don't try to create cache directories in-process, because they need special
+            // setup for accurate quota tracking. This ensures the cache dirs are always
+            // created through StorageManagerService.
+            return ensureExternalDirsExistOrFilter(dirs, false /* tryCreateInProcess */);
         }
-        return localExternalCacheDirs;
     }
 
     @Override
     public File[] getExternalMediaDirs() {
-        File[] localExternalMediaDirs = mExternalMediaDirs;
-        if (localExternalMediaDirs == null) {
-            synchronized (mDirsLock) {
-                localExternalMediaDirs = mExternalMediaDirs;
-                if (localExternalMediaDirs == null) {
-                    localExternalMediaDirs = Environment.buildExternalStorageAppMediaDirs(getPackageName());
-                    localExternalMediaDirs = ensureExternalDirsExistOrFilter(
-                            localExternalMediaDirs, true /* tryCreateInProcess */);
-                    mExternalMediaDirs = localExternalMediaDirs;
-                }
-            }
+        synchronized (mSync) {
+            File[] dirs = Environment.buildExternalStorageAppMediaDirs(getPackageName());
+            return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */);
         }
-        return localExternalMediaDirs;
     }
 
     /**
@@ -1121,22 +1046,16 @@
     }
 
     private File getDatabasesDir() {
-        File localDatabasesDir = mDatabasesDir;
-        if (localDatabasesDir == null) {
-            synchronized (mDirsLock) {
-                localDatabasesDir = mDatabasesDir;
-                if (localDatabasesDir == null) {
-                    if ("android".equals(getPackageName())) {
-                        localDatabasesDir = new File("/data/system");
-                    } else {
-                        localDatabasesDir = new File(getDataDir(), "databases");
-                    }
-                    ensurePrivateDirExists(localDatabasesDir);
-                    mDatabasesDir = localDatabasesDir;
+        synchronized (mSync) {
+            if (mDatabasesDir == null) {
+                if ("android".equals(getPackageName())) {
+                    mDatabasesDir = new File("/data/system");
+                } else {
+                    mDatabasesDir = new File(getDataDir(), "databases");
                 }
             }
+            return ensurePrivateDirExists(mDatabasesDir);
         }
-        return localDatabasesDir;
     }
 
     @Override
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index cd4c0bc..7337a7c 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3586,12 +3586,15 @@
      * Sets the token used for background operations for the pending intents associated with this
      * notification.
      *
+     * This token is automatically set during deserialization for you, you usually won't need to
+     * call this unless you want to change the existing token, if any.
+     *
      * @hide
      */
-    public void overrideAllowlistToken(IBinder token) {
-        mAllowlistToken = token;
+    public void clearAllowlistToken() {
+        mAllowlistToken = null;
         if (publicVersion != null) {
-            publicVersion.overrideAllowlistToken(token);
+            publicVersion.clearAllowlistToken();
         }
     }
 
@@ -7379,6 +7382,15 @@
     public static abstract class Style {
 
         /**
+         * @deprecated public access to the constructor of Style() is only useful for creating
+         * custom subclasses, but that has actually been impossible due to hidden abstract
+         * methods, so this constructor is now officially deprecated to clarify that this is
+         * intended to be disallowed.
+         */
+        @Deprecated
+        public Style() {}
+
+        /**
          * The number of items allowed simulatanously in the remote input history.
          * @hide
          */
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 9f2e473..7c803eb 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -776,7 +776,8 @@
 
     /**
      * Whether or not notifications posted to this channel can bypass the Do Not Disturb
-     * {@link NotificationManager#INTERRUPTION_FILTER_PRIORITY} mode.
+     * {@link NotificationManager#INTERRUPTION_FILTER_PRIORITY} mode when the active policy allows
+     * priority channels to bypass notification filtering.
      */
     public boolean canBypassDnd() {
         return mBypassDnd;
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/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index cb4ed058..d6f138f 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -45,11 +45,11 @@
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_STATUS_BAR;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SYSTEM_UPDATES;
-import static android.Manifest.permission.MANAGE_DEVICE_POLICY_THEFT_DETECTION;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WIFI;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WIPE_DATA;
 import static android.Manifest.permission.QUERY_ADMIN_POLICY;
+import static android.Manifest.permission.QUERY_DEVICE_STOLEN_STATE;
 import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY;
 import static android.Manifest.permission.SET_TIME;
 import static android.Manifest.permission.SET_TIME_ZONE;
@@ -17152,15 +17152,15 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(value = MANAGE_DEVICE_POLICY_THEFT_DETECTION)
+    @RequiresPermission(value = QUERY_DEVICE_STOLEN_STATE)
     @FlaggedApi(FLAG_DEVICE_THEFT_API_ENABLED)
-    public boolean isTheftDetectionTriggered() {
-        throwIfParentInstance("isTheftDetectionTriggered");
+    public boolean isDevicePotentiallyStolen() {
+        throwIfParentInstance("isDevicePotentiallyStolen");
         if (mService == null) {
             return false;
         }
         try {
-            return mService.isTheftDetectionTriggered(mContext.getPackageName());
+            return mService.isDevicePotentiallyStolen(mContext.getPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 03d0b0f..aea0246 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -581,7 +581,7 @@
     void setWifiSsidPolicy(String callerPackageName, in WifiSsidPolicy policy);
     WifiSsidPolicy getWifiSsidPolicy(String callerPackageName);
 
-    boolean isTheftDetectionTriggered(String callerPackageName);
+    boolean isDevicePotentiallyStolen(String callerPackageName);
 
     List<UserHandle> listForegroundAffiliatedUsers();
     void setDrawables(in List<DevicePolicyDrawableResource> drawables);
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/ProcessingSignal.java b/core/java/android/app/ondeviceintelligence/ProcessingSignal.java
index 3e543d2..c275cc7 100644
--- a/core/java/android/app/ondeviceintelligence/ProcessingSignal.java
+++ b/core/java/android/app/ondeviceintelligence/ProcessingSignal.java
@@ -75,7 +75,11 @@
 
 
     /**
-     * Sends a custom signal with the provided parameters. It also signals the remote callback
+     * Sends a custom signal with the provided parameters. If there are multiple concurrent
+     * requests to this method, the actionParams are queued in a blocking fashion, in the order they
+     * are received.
+     *
+     * It also signals the remote callback
      * with the same params if already configured, if not the action is queued to be sent when a
      * remote is configured. Similarly, on the receiver side, the callback will be invoked if
      * already set, if not all actions are queued to be sent to callback when it is set.
@@ -159,9 +163,9 @@
      * Sets the remote transport.
      *
      * If there are actions queued from {@link ProcessingSignal#sendSignal}, they are also
-     * sequentially sent to the remote.
+     * sequentially sent to the configured remote.
      *
-     * This method is guaranteed that the remote transport will not be called after it
+     * This method guarantees that the remote transport will not be called after it
      * has been removed.
      *
      * @param remote The remote transport, or null to remove.
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/servertransaction/WindowStateResizeItem.java b/core/java/android/app/servertransaction/WindowStateResizeItem.java
index 193b03c..fedffe1 100644
--- a/core/java/android/app/servertransaction/WindowStateResizeItem.java
+++ b/core/java/android/app/servertransaction/WindowStateResizeItem.java
@@ -28,6 +28,7 @@
 import android.os.Parcel;
 import android.os.RemoteException;
 import android.os.Trace;
+import android.util.Log;
 import android.util.MergedConfiguration;
 import android.view.IWindow;
 import android.view.InsetsState;
@@ -41,6 +42,8 @@
  */
 public class WindowStateResizeItem extends ClientTransactionItem {
 
+    private static final String TAG = "WindowStateResizeItem";
+
     private IWindow mWindow;
     private ClientWindowFrames mFrames;
     private boolean mReportDraw;
@@ -65,7 +68,9 @@
                     mAlwaysConsumeSystemBars, mDisplayId, mSyncSeqId, mDragResizing);
         } catch (RemoteException e) {
             // Should be a local call.
-            throw new RuntimeException(e);
+            // An exception could happen if the process is restarted. It is safe to ignore since
+            // the window should no longer exist.
+            Log.w(TAG, "The original window no longer exists in the new process", e);
         }
         Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
     }
diff --git a/core/java/android/app/wearable/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java
index fd72c49..df6d2a6 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;
 
@@ -246,7 +246,10 @@
      * @param executor Executor on which to run the consumer callback
      * @param statusConsumer A consumer that handles the status codes, which is returned
      *                 right after the call.
+     * @deprecated Use {@link #provideConnection(ParcelFileDescriptor, Executor, Consumer)} instead
+     *     to provide a remote wearable device connection to the WearableSensingService
      */
+    @Deprecated
     @RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)
     public void provideDataStream(
             @NonNull ParcelFileDescriptor parcelFileDescriptor,
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index 843158c..b4b96e2 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -252,6 +252,14 @@
     }
 
     /**
+     * @return true if the association is not revoked nor pending
+     * @hide
+     */
+    public boolean isActive() {
+        return !mRevoked && !mPending;
+    }
+
+    /**
      * @return the last time self reported disconnected for selfManaged only.
      * @hide
      */
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 8852705..535cebb 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
@@ -1281,6 +1280,26 @@
             264301586L; // buganizer id
 
     /**
+     * Excludes the packages the override is applied to from the camera compatibility treatment
+     * in free-form windowing mode for fixed-orientation apps.
+     *
+     * <p>In free-form windowing mode, the compatibility treatment emulates running on a portrait
+     * device by letterboxing the app window and changing the camera characteristics to what apps
+     * commonly expect in a portrait device: 90 and 270 degree sensor rotation for back and front
+     * cameras, respectively, and setting display rotation to 0.
+     *
+     * <p>Use this flag to disable the compatibility treatment for apps that do not respond well to
+     * the treatment.
+     *
+     * @hide
+     */
+    @ChangeId
+    @Overridable
+    @Disabled
+    public static final long OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT =
+            314961188L;
+
+    /**
      * This change id forces the packages it is applied to sandbox {@link android.view.View} API to
      * an activity bounds for:
      *
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 3a5383d..39b9149 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -693,7 +693,12 @@
      *
      * <p>If the caller is running on a managed profile, it'll return only the current profile.
      * Otherwise it'll return the same list as {@link UserManager#getUserProfiles()} would.
+     *
+     * <p> To get hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have
+     * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required.
      */
+    @RequiresPermission(conditional = true,
+            anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
     public List<UserHandle> getProfiles() {
         if (mUserManager.isManagedProfile()
                 || (android.multiuser.Flags.enableLauncherAppsHiddenProfileChecks()
@@ -751,11 +756,17 @@
      * list.</li>
      * </ul>
      *
+     * <p>If the user in question is a hidden profile
+     * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have
+     * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required.
+     *
      * @param packageName The specific package to query. If null, it checks all installed packages
      *            in the profile.
      * @param user The UserHandle of the profile.
      * @return List of launchable activities. Can be an empty list but will not be null.
      */
+    @RequiresPermission(conditional = true,
+            anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
     public List<LauncherActivityInfo> getActivityList(String packageName, UserHandle user) {
         logErrorForInvalidProfileAccess(user);
         try {
@@ -925,10 +936,16 @@
      * Returns the activity info for a given intent and user handle, if it resolves. Otherwise it
      * returns null.
      *
+     * <p>If the user in question is a hidden profile
+     * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have
+     * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required.
+     *
      * @param intent The intent to find a match for.
      * @param user The profile to look in for a match.
      * @return An activity info object if there is a match.
      */
+    @RequiresPermission(conditional = true,
+            anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
     public LauncherActivityInfo resolveActivity(Intent intent, UserHandle user) {
         logErrorForInvalidProfileAccess(user);
         try {
@@ -978,11 +995,17 @@
     /**
      * Starts a Main activity in the specified profile.
      *
+     * <p>If the user in question is a hidden profile
+     * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have
+     * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required.
+     *
      * @param component The ComponentName of the activity to launch
      * @param user The UserHandle of the profile
      * @param sourceBounds The Rect containing the source bounds of the clicked icon
      * @param opts Options to pass to startActivity
      */
+    @RequiresPermission(conditional = true,
+            anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
     public void startMainActivity(ComponentName component, UserHandle user, Rect sourceBounds,
             Bundle opts) {
         logErrorForInvalidProfileAccess(user);
@@ -1020,11 +1043,17 @@
      * Starts the settings activity to show the application details for a
      * package in the specified profile.
      *
+     * <p>If the user in question is a hidden profile
+     * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have
+     * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required.
+     *
      * @param component The ComponentName of the package to launch settings for.
      * @param user The UserHandle of the profile
      * @param sourceBounds The Rect containing the source bounds of the clicked icon
      * @param opts Options to pass to startActivity
      */
+    @RequiresPermission(conditional = true,
+            anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
     public void startAppDetailsActivity(ComponentName component, UserHandle user,
             Rect sourceBounds, Bundle opts) {
         logErrorForInvalidProfileAccess(user);
@@ -1135,11 +1164,17 @@
     /**
      * Checks if the package is installed and enabled for a profile.
      *
+     * <p>If the user in question is a hidden profile
+     * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have
+     * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required.
+     *
      * @param packageName The package to check.
      * @param user The UserHandle of the profile.
      *
      * @return true if the package exists and is enabled.
      */
+    @RequiresPermission(conditional = true,
+            anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
     public boolean isPackageEnabled(String packageName, UserHandle user) {
         logErrorForInvalidProfileAccess(user);
         try {
@@ -1157,6 +1192,10 @@
      * <p>The contents of this {@link Bundle} are supposed to be a contract between the suspending
      * app and the launcher.
      *
+     * <p>If the user in question is a hidden profile
+     * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have
+     * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required.
+     *
      * <p>Note: This just returns whatever extras were provided to the system, <em>which might
      * even be {@code null}.</em>
      *
@@ -1168,6 +1207,8 @@
      * @see Callback#onPackagesSuspended(String[], UserHandle, Bundle)
      * @see PackageManager#isPackageSuspended()
      */
+    @RequiresPermission(conditional = true,
+            anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
     public @Nullable Bundle getSuspendedPackageLauncherExtras(String packageName, UserHandle user) {
         logErrorForInvalidProfileAccess(user);
         try {
@@ -1182,10 +1223,16 @@
      * could be done because the package was marked as distracting to the user via
      * {@code PackageManager.setDistractingPackageRestrictions(String[], int)}.
      *
+     * <p>If the user in question is a hidden profile
+     * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have
+     * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required.
+     *
      * @param packageName The package for which to check.
      * @param user the {@link UserHandle} of the profile.
      * @return
      */
+    @RequiresPermission(conditional = true,
+            anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
     public boolean shouldHideFromSuggestions(@NonNull String packageName,
             @NonNull UserHandle user) {
         Objects.requireNonNull(packageName, "packageName");
@@ -1200,6 +1247,10 @@
     /**
      * Returns {@link ApplicationInfo} about an application installed for a specific user profile.
      *
+     * <p>If the user in question is a hidden profile
+     * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have
+     * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required.
+     *
      * @param packageName The package name of the application
      * @param flags Additional option flags {@link PackageManager#getApplicationInfo}
      * @param user The UserHandle of the profile.
@@ -1208,6 +1259,8 @@
      *         {@code null} if the package isn't installed for the given profile, or the profile
      *         isn't enabled.
      */
+    @RequiresPermission(conditional = true,
+            anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
     public ApplicationInfo getApplicationInfo(@NonNull String packageName,
             @ApplicationInfoFlagsBits int flags, @NonNull UserHandle user)
             throws PackageManager.NameNotFoundException {
@@ -1257,11 +1310,17 @@
      * <p>The activity may still not be exported, in which case {@link #startMainActivity} will
      * throw a {@link SecurityException} unless the caller has the same UID as the target app's.
      *
+     * <p>If the user in question is a hidden profile
+     * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have
+     * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required.
+     *
      * @param component The activity to check.
      * @param user The UserHandle of the profile.
      *
      * @return true if the activity exists and is enabled.
      */
+    @RequiresPermission(conditional = true,
+            anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
     public boolean isActivityEnabled(ComponentName component, UserHandle user) {
         logErrorForInvalidProfileAccess(user);
         try {
@@ -1816,8 +1875,14 @@
     /**
      * Registers a callback for changes to packages in this user and managed profiles.
      *
+     * <p>To receive callbacks for hidden profile{@link UserManager.USER_TYPE_PROFILE_PRIVATE},
+     * caller should have {@link android.app.role.RoleManager.ROLE_HOME} and either of the
+     * permissions required.
+     *
      * @param callback The callback to register.
      */
+    @RequiresPermission(conditional = true,
+            anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
     public void registerCallback(Callback callback) {
         registerCallback(callback, null);
     }
@@ -1825,9 +1890,15 @@
     /**
      * Registers a callback for changes to packages in this user and managed profiles.
      *
+     * <p>To receive callbacks for hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE},
+     * caller should have {@link android.app.role.RoleManager.ROLE_HOME} and either of the
+     * permissions required.
+     *
      * @param callback The callback to register.
      * @param handler that should be used to post callbacks on, may be null.
      */
+    @RequiresPermission(conditional = true,
+            anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
     public void registerCallback(Callback callback, Handler handler) {
         synchronized (this) {
             if (callback != null && findCallbackLocked(callback) < 0) {
@@ -2275,8 +2346,14 @@
      * package name in the app's manifest, have the android.permission.QUERY_ALL_PACKAGES, or be
      * the session owner to retrieve these details.
      *
+     * <p>If the user in question is a hidden profile
+     * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have
+     * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required.
+     *
      * @see PackageInstaller#getAllSessions()
      */
+    @RequiresPermission(conditional = true,
+            anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
     public @NonNull List<SessionInfo> getAllPackageInstallerSessions() {
         try {
             return mService.getAllSessions(mContext.getPackageName()).getList();
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 48a7cc9..2d32aed 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -108,6 +108,7 @@
     is_fixed_read_only: true
 }
 
+# This flag guards the private space feature and all its implementations excluding the APIs. APIs are guarded by android.os.Flags.allow_private_profile.
 flag {
     name: "enable_private_space_features"
     namespace: "profile_experiences"
@@ -192,3 +193,10 @@
     bug: "290333800"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "delete_private_space_from_reset"
+    namespace: "profile_experiences"
+    description: "Add entrypoint in Settings Reset options for deleting private space when lock is forgotten"
+    bug: "329601751"
+}
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/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index dfb77c0..faa2c70 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -34,22 +34,17 @@
 import android.util.LruCache;
 import android.util.Pair;
 import android.util.Printer;
-import com.android.internal.util.RingBuffer;
 import dalvik.system.BlockGuard;
 import dalvik.system.CloseGuard;
-
 import java.io.File;
 import java.io.IOException;
 import java.lang.ref.Reference;
 import java.nio.file.FileSystems;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.time.Instant;
-import java.time.ZoneId;
-import java.time.format.DateTimeFormatter;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Date;
-import java.util.Locale;
 import java.util.Map;
 import java.util.function.BinaryOperator;
 import java.util.function.UnaryOperator;
@@ -190,7 +185,7 @@
             SQLiteDatabaseConfiguration configuration,
             int connectionId, boolean primaryConnection) {
         mPool = pool;
-        mRecentOperations = new OperationLog();
+        mRecentOperations = new OperationLog(mPool);
         mConfiguration = new SQLiteDatabaseConfiguration(configuration);
         mConnectionId = connectionId;
         mIsPrimaryConnection = primaryConnection;
@@ -312,16 +307,6 @@
         }
     }
 
-    /** Record the start of a transaction for logging and debugging. */
-    void recordBeginTransaction(String mode) {
-        mRecentOperations.beginTransaction(mode);
-    }
-
-    /** Record the end of a transaction for logging and debugging. */
-    void recordEndTransaction(boolean successful) {
-        mRecentOperations.endTransaction(successful);
-    }
-
     private void setPageSize() {
         if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
             final long newValue = SQLiteGlobal.getDefaultPageSize();
@@ -1352,7 +1337,6 @@
         }
         printer.println("  isPrimaryConnection: " + mIsPrimaryConnection);
         printer.println("  onlyAllowReadOnlyOperations: " + mOnlyAllowReadOnlyOperations);
-        printer.println("  totalLongOperations: " + mRecentOperations.getTotalLongOperations());
 
         mRecentOperations.dump(printer);
 
@@ -1611,39 +1595,51 @@
         }
     }
 
-    private final class OperationLog {
+    private static final class OperationLog {
         private static final int MAX_RECENT_OPERATIONS = 20;
         private static final int COOKIE_GENERATION_SHIFT = 8;
         private static final int COOKIE_INDEX_MASK = 0xff;
 
-        // Operations over 2s are long.  Save the last ten.
-        private static final long LONG_OPERATION_THRESHOLD_MS = 2_000;
-        private static final int MAX_LONG_OPERATIONS = 10;
-
         private final Operation[] mOperations = new Operation[MAX_RECENT_OPERATIONS];
-        private int mIndex = -1;
-        private int mGeneration = 0;
-        private final Operation mTransaction = new Operation();
+        private int mIndex;
+        private int mGeneration;
+        private final SQLiteConnectionPool mPool;
         private long mResultLong = Long.MIN_VALUE;
         private String mResultString;
 
-        private final RingBuffer<Operation> mLongOperations =
-                new RingBuffer<>(()->{return new Operation();},
-                        (n) ->{return new Operation[n];},
-                        MAX_LONG_OPERATIONS);
-        private int mTotalLongOperations = 0;
+        OperationLog(SQLiteConnectionPool pool) {
+            mPool = pool;
+        }
 
         public int beginOperation(String kind, String sql, Object[] bindArgs) {
             mResultLong = Long.MIN_VALUE;
             mResultString = null;
 
             synchronized (mOperations) {
-                Operation operation = newOperationLocked();
+                final int index = (mIndex + 1) % MAX_RECENT_OPERATIONS;
+                Operation operation = mOperations[index];
+                if (operation == null) {
+                    operation = new Operation();
+                    mOperations[index] = operation;
+                } else {
+                    operation.mFinished = false;
+                    operation.mException = null;
+                    if (operation.mBindArgs != null) {
+                        operation.mBindArgs.clear();
+                    }
+                }
+                operation.mStartWallTime = System.currentTimeMillis();
+                operation.mStartTime = SystemClock.uptimeMillis();
                 operation.mKind = kind;
                 operation.mSql = sql;
+                operation.mPath = mPool.getPath();
+                operation.mResultLong = Long.MIN_VALUE;
+                operation.mResultString = null;
                 if (bindArgs != null) {
                     if (operation.mBindArgs == null) {
                         operation.mBindArgs = new ArrayList<Object>();
+                    } else {
+                        operation.mBindArgs.clear();
                     }
                     for (int i = 0; i < bindArgs.length; i++) {
                         final Object arg = bindArgs[i];
@@ -1655,44 +1651,16 @@
                         }
                     }
                 }
+                operation.mCookie = newOperationCookieLocked(index);
                 if (Trace.isTagEnabled(Trace.TRACE_TAG_DATABASE)) {
                     Trace.asyncTraceBegin(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
                             operation.mCookie);
                 }
+                mIndex = index;
                 return operation.mCookie;
             }
         }
 
-        public void beginTransaction(String kind) {
-            synchronized (mOperations) {
-                Operation operation = newOperationLocked();
-                operation.mKind = kind;
-                mTransaction.copyFrom(operation);
-
-                if (Trace.isTagEnabled(Trace.TRACE_TAG_DATABASE)) {
-                    Trace.asyncTraceBegin(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
-                            operation.mCookie);
-                }
-            }
-        }
-
-        /**
-         * Fetch a new operation from the ring buffer.  The operation is properly initialized.
-         * This advances mIndex to point to the next element.
-         */
-        private Operation newOperationLocked() {
-            final int index = (mIndex + 1) % MAX_RECENT_OPERATIONS;
-            Operation operation = mOperations[index];
-            if (operation == null) {
-                mOperations[index] = new Operation();
-                operation = mOperations[index];
-            }
-            operation.start();
-            operation.mCookie = newOperationCookieLocked(index);
-            mIndex = index;
-            return operation;
-        }
-
         public void failOperation(int cookie, Exception ex) {
             synchronized (mOperations) {
                 final Operation operation = getOperationLocked(cookie);
@@ -1716,20 +1684,6 @@
             }
         }
 
-        public boolean endTransaction(boolean success) {
-            synchronized (mOperations) {
-                mTransaction.mResultLong = success ? 1 : 0;
-                final long execTime = finishOperationLocked(mTransaction);
-                final Operation operation = getOperationLocked(mTransaction.mCookie);
-                if (operation != null) {
-                    operation.copyFrom(mTransaction);
-                }
-                mTransaction.setEmpty();
-                return NoPreloadHolder.DEBUG_LOG_SLOW_QUERIES
-                        && SQLiteDebug.shouldLogSlowQuery(execTime);
-            }
-        }
-
         public void logOperation(int cookie, String detail) {
             synchronized (mOperations) {
                 logOperationLocked(cookie, detail);
@@ -1751,7 +1705,9 @@
                     Trace.asyncTraceEnd(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
                             operation.mCookie);
                 }
-                final long execTime = finishOperationLocked(operation);
+                operation.mEndTime = SystemClock.uptimeMillis();
+                operation.mFinished = true;
+                final long execTime = operation.mEndTime - operation.mStartTime;
                 mPool.onStatementExecuted(execTime);
                 return NoPreloadHolder.DEBUG_LOG_SLOW_QUERIES && SQLiteDebug.shouldLogSlowQuery(
                         execTime);
@@ -1776,22 +1732,10 @@
             return generation << COOKIE_GENERATION_SHIFT | index;
         }
 
-        /** Close out the operation and return the elapsed time. */
-        private long finishOperationLocked(Operation operation) {
-            operation.mEndTime = SystemClock.uptimeMillis();
-            operation.mFinished = true;
-            final long elapsed = operation.mEndTime - operation.mStartTime;
-            if (elapsed > LONG_OPERATION_THRESHOLD_MS) {
-                mLongOperations.getNextSlot().copyFrom(operation);
-                mTotalLongOperations++;
-            }
-            return elapsed;
-        }
-
         private Operation getOperationLocked(int cookie) {
             final int index = cookie & COOKIE_INDEX_MASK;
             final Operation operation = mOperations[index];
-            return (operation != null && operation.mCookie == cookie) ? operation : null;
+            return operation.mCookie == cookie ? operation : null;
         }
 
         public String describeCurrentOperation() {
@@ -1806,87 +1750,48 @@
             }
         }
 
-        /**
-         * Dump an Operation if it is not in the recent operations list.  Return 1 if the
-         * operation was dumped and 0 if not.
-         */
-        private int dumpIfNotRecentLocked(Printer pw, Operation op, int counter) {
-            if (op == null || op.isEmpty() || getOperationLocked(op.mCookie) != null) {
-                return 0;
-            }
-            pw.println(op.describe(counter));
-            return 1;
-        }
-
-        private void dumpRecentLocked(Printer printer) {
+        public void dump(Printer printer) {
             synchronized (mOperations) {
                 printer.println("  Most recently executed operations:");
                 int index = mIndex;
-                if (index == 0) {
-                    printer.println("    <none>");
-                    return;
-                }
-
-                // Operations are dumped in order of most recent first.
-                int counter = 0;
-                int n = 0;
                 Operation operation = mOperations[index];
-                do {
-                    printer.println(operation.describe(counter));
+                if (operation != null) {
+                    // Note: SimpleDateFormat is not thread-safe, cannot be compile-time created,
+                    // and is relatively expensive to create during preloading. This method is only
+                    // used when dumping a connection, which is a rare (mainly error) case.
+                    SimpleDateFormat opDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+                    int n = 0;
+                    do {
+                        StringBuilder msg = new StringBuilder();
+                        msg.append("    ").append(n).append(": [");
+                        String formattedStartTime = opDF.format(new Date(operation.mStartWallTime));
+                        msg.append(formattedStartTime);
+                        msg.append("] ");
+                        operation.describe(msg, false); // Never dump bingargs in a bugreport
+                        printer.println(msg.toString());
 
-                    if (index > 0) {
-                        index -= 1;
-                    } else {
-                        index = MAX_RECENT_OPERATIONS - 1;
-                    }
-                    n++;
-                    counter++;
-                    operation = mOperations[index];
-                } while (operation != null && n < MAX_RECENT_OPERATIONS);
-                counter += dumpIfNotRecentLocked(printer, mTransaction, counter);
-            }
-        }
-
-        private void dumpLongLocked(Printer printer) {
-            printer.println("  Operations exceeding " + LONG_OPERATION_THRESHOLD_MS + "ms:");
-            if (mLongOperations.isEmpty()) {
-                printer.println("    <none>");
-                return;
-            }
-            Operation[] longOps = mLongOperations.toArray();
-            for (int i = 0; i < longOps.length; i++) {
-                if (longOps[i] != null) {
-                    printer.println(longOps[i].describe(i));
+                        if (index > 0) {
+                            index -= 1;
+                        } else {
+                            index = MAX_RECENT_OPERATIONS - 1;
+                        }
+                        n += 1;
+                        operation = mOperations[index];
+                    } while (operation != null && n < MAX_RECENT_OPERATIONS);
+                } else {
+                    printer.println("    <none>");
                 }
             }
         }
-
-        public long getTotalLongOperations() {
-            return mTotalLongOperations;
-        }
-
-        public void dump(Printer printer) {
-            synchronized (mOperations) {
-                dumpRecentLocked(printer);
-                dumpLongLocked(printer);
-            }
-        }
     }
 
-    private final class Operation {
+    private static final class Operation {
         // Trim all SQL statements to 256 characters inside the trace marker.
         // This limit gives plenty of context while leaving space for other
         // entries in the trace buffer (and ensures atrace doesn't truncate the
         // marker for us, potentially losing metadata in the process).
         private static final int MAX_TRACE_METHOD_NAME_LEN = 256;
 
-        // The reserved start time that indicates the Operation is empty.
-        private static final long EMPTY_OPERATION = -1;
-
-        // The formatter for the timestamp.
-        private static final DateTimeFormatter sDateTime =
-                DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS", Locale.US);
-
         public long mStartWallTime; // in System.currentTimeMillis()
         public long mStartTime; // in SystemClock.uptimeMillis();
         public long mEndTime; // in SystemClock.uptimeMillis();
@@ -1896,58 +1801,16 @@
         public boolean mFinished;
         public Exception mException;
         public int mCookie;
+        public String mPath;
         public long mResultLong; // MIN_VALUE means "value not set".
         public String mResultString;
 
-        /** Reset the object to begin a new operation. */
-        void start() {
-            mStartWallTime = System.currentTimeMillis();
-            mStartTime = SystemClock.uptimeMillis();
-            mEndTime = Long.MIN_VALUE;
-            mKind = null;
-            mSql = null;
-            if (mBindArgs != null) mBindArgs.clear();
-            mFinished = false;
-            mException = null;
-            mCookie = -1;
-            mResultLong = Long.MIN_VALUE;
-            mResultString = null;
-        }
-
-        /**
-         * Initialize from the source object.  This is meant to clone the object for use in a
-         * transaction operation.  To that end, the local bind args are set to null.
-         */
-        void copyFrom(Operation r) {
-            mStartWallTime = r.mStartWallTime;
-            mStartTime = r.mStartTime;
-            mEndTime = r.mEndTime;
-            mKind = r.mKind;
-            mSql = r.mSql;
-            mBindArgs = null;
-            mFinished = r.mFinished;
-            mException = r.mException;
-            mCookie = r.mCookie;
-            mResultLong = r.mResultLong;
-            mResultString = r.mResultString;
-        }
-
-        /** Mark the operation empty. */
-        void setEmpty() {
-            mStartWallTime = EMPTY_OPERATION;
-        }
-
-        /** Return true if the operation is empty. */
-        boolean isEmpty() {
-            return mStartWallTime == EMPTY_OPERATION;
-        }
-
         public void describe(StringBuilder msg, boolean allowDetailedLog) {
             msg.append(mKind);
             if (mFinished) {
                 msg.append(" took ").append(mEndTime - mStartTime).append("ms");
             } else {
-                msg.append(" started ").append(SystemClock.uptimeMillis() - mStartTime)
+                msg.append(" started ").append(System.currentTimeMillis() - mStartWallTime)
                         .append("ms ago");
             }
             msg.append(" - ").append(getStatus());
@@ -1976,7 +1839,7 @@
                 }
                 msg.append("]");
             }
-            msg.append(", path=").append(mPool.getPath());
+            msg.append(", path=").append(mPath);
             if (mException != null) {
                 msg.append(", exception=\"").append(mException.getMessage()).append("\"");
             }
@@ -1988,21 +1851,6 @@
             }
         }
 
-        /**
-         * Convert a wall-clock time in milliseconds to logcat format.
-         */
-        private String timeString(long millis) {
-            return sDateTime.withZone(ZoneId.systemDefault()).format(Instant.ofEpochMilli(millis));
-        }
-
-        public String describe(int n) {
-            final StringBuilder msg = new StringBuilder();
-            final String start = timeString(mStartWallTime);
-            msg.append("    ").append(n).append(": [").append(start).append("] ");
-            describe(msg, false); // Never dump bingargs in a bugreport
-            return msg.toString();
-        }
-
         private String getStatus() {
             if (!mFinished) {
                 return "running";
@@ -2016,6 +1864,7 @@
                 return methodName.substring(0, MAX_TRACE_METHOD_NAME_LEN);
             return methodName;
         }
+
     }
 
     /**
diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java
index 15d7d66..ad335b6 100644
--- a/core/java/android/database/sqlite/SQLiteConnectionPool.java
+++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java
@@ -1175,7 +1175,7 @@
                     + ", isLegacyCompatibilityWalEnabled=" + isCompatibilityWalEnabled
                     + ", journalMode=" + TextUtils.emptyIfNull(mConfiguration.resolveJournalMode())
                     + ", syncMode=" + TextUtils.emptyIfNull(mConfiguration.resolveSyncMode()));
-            printer.println("  IsReadOnlyDatabase: " + mConfiguration.isReadOnlyDatabase());
+            printer.println("  IsReadOnlyDatabase=" + mConfiguration.isReadOnlyDatabase());
 
             if (isCompatibilityWalEnabled) {
                 printer.println("  Compatibility WAL enabled: wal_syncmode="
diff --git a/core/java/android/database/sqlite/SQLiteSession.java b/core/java/android/database/sqlite/SQLiteSession.java
index 3b14d9d..7d9f02d 100644
--- a/core/java/android/database/sqlite/SQLiteSession.java
+++ b/core/java/android/database/sqlite/SQLiteSession.java
@@ -312,15 +312,6 @@
                 cancellationSignal);
     }
 
-    private String modeString(int transactionMode) {
-        switch (transactionMode) {
-            case TRANSACTION_MODE_IMMEDIATE: return "TRANSACTION-IMMEDIATE";
-            case TRANSACTION_MODE_EXCLUSIVE: return "TRANSACTION-EXCLUSIVE";
-            case TRANSACTION_MODE_DEFERRED:  return "TRANSACTION-DEFERRED";
-            default: return "TRANSACTION";
-        }
-    }
-
     private void beginTransactionUnchecked(int transactionMode,
             SQLiteTransactionListener transactionListener, int connectionFlags,
             CancellationSignal cancellationSignal) {
@@ -330,7 +321,6 @@
 
         if (mTransactionStack == null) {
             acquireConnection(null, connectionFlags, cancellationSignal); // might throw
-            mConnection.recordBeginTransaction(modeString(transactionMode));
         }
         try {
             // Set up the transaction such that we can back out safely
@@ -475,7 +465,6 @@
                     mConnection.execute("ROLLBACK;", null, cancellationSignal); // might throw
                 }
             } finally {
-                mConnection.recordEndTransaction(successful);
                 releaseConnection(); // might throw
             }
         }
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 0208fed..be9f0a0 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -295,7 +295,7 @@
          * called.
          *
          * @param view The customized view information.
-         * @return This builder.re
+         * @return This builder.
          */
         @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
         @NonNull
diff --git a/core/java/android/hardware/camera2/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 083d49f..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;
 
     /**
@@ -227,14 +227,18 @@
     private static List<Size> generateSupportedSizes(List<SizeList> sizesList,
                                                      Integer format,
                                                      StreamConfigurationMap streamMap) {
-        // Per API contract it is assumed that the extension is able to support all
-        // camera advertised sizes for a given format in case it doesn't return
-        // a valid non-empty size list.
         ArrayList<Size> ret = getSupportedSizes(sizesList, format);
-        Size[] supportedSizes = streamMap.getOutputSizes(format);
-        if ((ret.isEmpty()) && (supportedSizes != null)) {
-            ret.addAll(Arrays.asList(supportedSizes));
+
+        if (format == ImageFormat.JPEG || format == ImageFormat.YUV_420_888) {
+            // Per API contract it is assumed that the extension is able to support all
+            // camera advertised sizes for JPEG and YUV_420_888 in case it doesn't return
+            // a valid non-empty size list.
+            Size[] supportedSizes = streamMap.getOutputSizes(format);
+            if ((ret.isEmpty()) && (supportedSizes != null)) {
+                ret.addAll(Arrays.asList(supportedSizes));
+            }
         }
+
         return ret;
     }
 
@@ -559,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);
                 }
 
@@ -762,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);
         }
 
@@ -1544,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/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index d2e4a61..b43a900 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -47,6 +47,7 @@
 import android.hardware.camera2.utils.CameraIdAndSessionConfiguration;
 import android.hardware.camera2.utils.ConcurrentCameraIdCombination;
 import android.hardware.camera2.utils.ExceptionUtils;
+import android.hardware.devicestate.DeviceState;
 import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.display.DisplayManager;
 import android.os.Binder;
@@ -204,14 +205,12 @@
             mDeviceStateListeners.add(new WeakReference<>(listener));
         }
 
+        @SuppressWarnings("FlaggedApi")
         @Override
-        public final void onBaseStateChanged(int state) {
-            handleStateChange(state);
-        }
-
-        @Override
-        public final void onStateChanged(int state) {
-            handleStateChange(state);
+        public void onDeviceStateChanged(DeviceState state) {
+            // Suppressing the FlaggedAPI warning as this specific API isn't new, just moved to
+            // system API which requires it to be flagged.
+            handleStateChange(state.getIdentifier());
         }
     }
 
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/CameraSessionConfig.aidl b/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl
index c4f653c..9d46b55 100644
--- a/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl
+++ b/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl
@@ -25,5 +25,5 @@
     CameraMetadataNative sessionParameter;
     int sessionTemplateId;
     int sessionType;
-    int colorSpace;
+    int colorSpace = -1;
 }
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index 5b7f8bb..6d9b51cb 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -348,7 +348,23 @@
             cameraOutput.setTimestampBase(OutputConfiguration.TIMESTAMP_BASE_SENSOR);
             cameraOutput.setReadoutTimestampEnabled(false);
             cameraOutput.setPhysicalCameraId(output.physicalCameraId);
-            cameraOutput.setDynamicRangeProfile(output.dynamicRangeProfile);
+            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);
         }
@@ -363,9 +379,15 @@
         SessionConfiguration sessionConfiguration = new SessionConfiguration(sessionType,
                 outputList, new CameraExtensionUtils.HandlerExecutor(mHandler),
                 new SessionStateHandler());
-        if (sessionConfig.colorSpace != ColorSpaceProfiles.UNSPECIFIED) {
-            sessionConfiguration.setColorSpace(
-                    ColorSpace.Named.values()[sessionConfig.colorSpace]);
+        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())) {
diff --git a/core/java/android/hardware/devicestate/DeviceState.java b/core/java/android/hardware/devicestate/DeviceState.java
index 76888f3..b214da2 100644
--- a/core/java/android/hardware/devicestate/DeviceState.java
+++ b/core/java/android/hardware/devicestate/DeviceState.java
@@ -52,78 +52,6 @@
 @FlaggedApi(android.hardware.devicestate.feature.flags.Flags.FLAG_DEVICE_STATE_PROPERTY_API)
 public final class DeviceState {
     /**
-     * Flag that indicates override requests should be cancelled when this device state becomes the
-     * base device state.
-     * @hide
-     * @deprecated use {@link #PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS}
-     */
-    @Deprecated
-    public static final int FLAG_CANCEL_OVERRIDE_REQUESTS = 1 << 0;
-
-    /**
-     * Flag that indicates this device state is inaccessible for applications to be placed in. This
-     * could be a device-state where the {@link Display#DEFAULT_DISPLAY} is not enabled.
-     * @hide
-     * @deprecated use {@link #PROPERTY_APP_INACCESSIBLE}
-     */
-    @Deprecated
-    public static final int FLAG_APP_INACCESSIBLE = 1 << 1;
-
-    /**
-     * Some device states can be both entered through a physical configuration as well as emulation
-     * through {@link DeviceStateManager#requestState}, while some states can only be entered
-     * through emulation and have no physical configuration to match.
-     *
-     * This flag indicates that the corresponding state can only be entered through emulation.
-     * @hide
-     * @deprecated use {@link #PROPERTY_EMULATED_ONLY}
-     */
-    @Deprecated
-    public static final int FLAG_EMULATED_ONLY = 1 << 2;
-
-    /**
-     * This flag indicates that the corresponding state should be automatically canceled when the
-     * requesting app is no longer on top. The app is considered not on top when (1) the top
-     * activity in the system is from a different app, (2) the device is in sleep mode, or
-     * (3) the keyguard shows up.
-     * @hide
-     * @deprecated use {@link #PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP}
-     */
-    @Deprecated
-    public static final int FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP = 1 << 3;
-
-    /**
-     * This flag indicates that the corresponding state should be disabled when the device is
-     * overheating and reaching the critical status.
-     * @hide
-     * @deprecated use {@link #PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL}
-     */
-    @Deprecated
-    public static final int FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL = 1 << 4;
-
-    /**
-     * This flag indicates that the corresponding state should be disabled when power save mode
-     * is enabled.
-     * @hide
-     * @deprecated use {@link #PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE}
-     */
-    @Deprecated
-    public static final int FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE = 1 << 5;
-
-    /** @hide */
-    @IntDef(prefix = {"FLAG_"}, flag = true, value = {
-            FLAG_CANCEL_OVERRIDE_REQUESTS,
-            FLAG_APP_INACCESSIBLE,
-            FLAG_EMULATED_ONLY,
-            FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP,
-            FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL,
-            FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE
-    })
-    @Deprecated
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface DeviceStateFlags {}
-
-    /**
      * Property that indicates that a fold-in style foldable device is currently in a fully closed
      * configuration.
      */
@@ -302,42 +230,11 @@
     @NonNull
     private final DeviceState.Configuration mDeviceStateConfiguration;
 
-    @DeviceStateFlags
-    private final int mFlags;
-
     /** @hide */
+    @TestApi
     public DeviceState(@NonNull DeviceState.Configuration deviceStateConfiguration) {
         Objects.requireNonNull(deviceStateConfiguration, "Device StateConfiguration is null");
         mDeviceStateConfiguration = deviceStateConfiguration;
-        mFlags = 0;
-    }
-
-    /** @hide */
-    public DeviceState(
-            @IntRange(from = MINIMUM_DEVICE_STATE_IDENTIFIER, to =
-                    MAXIMUM_DEVICE_STATE_IDENTIFIER) int identifier,
-            @NonNull String name,
-            @NonNull Set<@DeviceStateProperties Integer> properties) {
-        mDeviceStateConfiguration = new DeviceState.Configuration(identifier, name, properties,
-                Collections.emptySet());
-        mFlags = 0;
-    }
-
-    /**
-     * @deprecated Deprecated in favor of {@link #DeviceState(int, String, Set)}
-     * @hide
-     */
-    // TODO(b/325124054): Make non-default and remove deprecated callback methods.
-    @Deprecated
-    public DeviceState(
-            @IntRange(from = MINIMUM_DEVICE_STATE_IDENTIFIER, to =
-                    MAXIMUM_DEVICE_STATE_IDENTIFIER) int identifier,
-            @NonNull String name,
-            @DeviceStateFlags int flags) {
-
-        mDeviceStateConfiguration = new DeviceState.Configuration(identifier, name,
-                Collections.emptySet(), Collections.emptySet());
-        mFlags = flags;
     }
 
     /** Returns the unique identifier for the device state. */
@@ -352,17 +249,6 @@
         return mDeviceStateConfiguration.getName();
     }
 
-    /**
-     * @hide
-     * @deprecated in favor of {@link #hasProperty(int)} method
-     */
-    // TODO(b/325124054): Make non-default and remove deprecated callback methods.
-    @Deprecated
-    @DeviceStateFlags
-    public int getFlags() {
-        return mFlags;
-    }
-
     @Override
     public String toString() {
         return "DeviceState{" + "identifier=" + mDeviceStateConfiguration.getIdentifier()
@@ -388,16 +274,6 @@
         return Objects.hash(mDeviceStateConfiguration);
     }
 
-    /** Checks if a specific flag is set
-     * @hide
-     * @deprecated in favor of {@link #hasProperty(int)}
-     */
-    // TODO(b/325124054): Make non-default and remove deprecated callback methods.
-    @Deprecated
-    public boolean hasFlag(int flagToCheckFor) {
-        return (mFlags & flagToCheckFor) == flagToCheckFor;
-    }
-
     /**
      * Checks if a specific property is set on this state
      */
@@ -438,6 +314,7 @@
      * @see DeviceStateManager
      * @hide
      */
+    @TestApi
     public static final class Configuration implements Parcelable {
         /** Unique identifier for the device state. */
         @IntRange(from = MINIMUM_DEVICE_STATE_IDENTIFIER, to = MAXIMUM_DEVICE_STATE_IDENTIFIER)
@@ -563,29 +440,35 @@
         };
 
         /** @hide */
-        public static class Builder {
+        @TestApi
+        public static final class Builder {
             private final int mIdentifier;
+            @NonNull
             private final String mName;
+            @NonNull
             private Set<@SystemDeviceStateProperties Integer> mSystemProperties =
                     Collections.emptySet();
+            @NonNull
             private Set<@PhysicalDeviceStateProperties Integer> mPhysicalProperties =
                     Collections.emptySet();
 
-            public Builder(int identifier, String name) {
+            public Builder(int identifier, @NonNull String name) {
                 mIdentifier = identifier;
                 mName = name;
             }
 
             /** Sets the system properties for this {@link DeviceState.Configuration.Builder} */
+            @NonNull
             public Builder setSystemProperties(
-                    Set<@SystemDeviceStateProperties Integer> systemProperties) {
+                    @NonNull Set<@SystemDeviceStateProperties Integer> systemProperties) {
                 mSystemProperties = systemProperties;
                 return this;
             }
 
             /** Sets the system properties for this {@link DeviceState.Configuration.Builder} */
+            @NonNull
             public Builder setPhysicalProperties(
-                    Set<@PhysicalDeviceStateProperties Integer> physicalProperties) {
+                    @NonNull Set<@PhysicalDeviceStateProperties Integer> physicalProperties) {
                 mPhysicalProperties = physicalProperties;
                 return this;
             }
@@ -594,6 +477,7 @@
              * Returns a new {@link DeviceState.Configuration} whose values match the values set on
              * the builder.
              */
+            @NonNull
             public DeviceState.Configuration build() {
                 return new DeviceState.Configuration(mIdentifier, mName, mSystemProperties,
                         mPhysicalProperties);
diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java
index a4c3833..febc24c1 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManager.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManager.java
@@ -49,6 +49,7 @@
      *
      * @hide
      */
+    @TestApi
     public static final int INVALID_DEVICE_STATE_IDENTIFIER = -1;
 
     /**
@@ -96,20 +97,6 @@
     /**
      * Returns the list of device states that are supported and can be requested with
      * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
-     * @deprecated use {@link #getSupportedDeviceStates()}
-     * @hide
-     */
-    // TODO(b/325124054): Make non-default and remove deprecated callback methods.
-    @TestApi
-    @Deprecated
-    @NonNull
-    public int[] getSupportedStates() {
-        return mGlobal.getSupportedStates();
-    }
-
-    /**
-     * Returns the list of device states that are supported and can be requested with
-     * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
      */
     @NonNull
     public List<DeviceState> getSupportedDeviceStates() {
@@ -239,23 +226,6 @@
          * Guaranteed to be called once on registration of the callback with the initial value and
          * then on every subsequent change in the supported states.
          *
-         * @param supportedStates the new supported states.
-         *
-         * @see DeviceStateManager#getSupportedStates()
-         * @deprecated use {@link #onSupportedStatesChanged(List)}
-         * @hide
-         */
-        // TODO(b/325124054): Make non-default and remove deprecated callback methods.
-        @TestApi
-        @Deprecated
-        default void onSupportedStatesChanged(@NonNull int[] supportedStates) {}
-
-        /**
-         * Called in response to a change in the states supported by the device.
-         * <p>
-         * Guaranteed to be called once on registration of the callback with the initial value and
-         * then on every subsequent change in the supported states.
-         *
          * The supported device states may change due to certain states becoming unavailable
          * due to device configuration or device conditions such as if the device is too hot or
          * external monitors have been connected.
@@ -267,42 +237,6 @@
         default void onSupportedStatesChanged(@NonNull List<DeviceState> supportedStates) {}
 
         /**
-         * Called in response to a change in the base device state.
-         * <p>
-         * The base state is the state of the device without considering any requests made through
-         * calls to {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}
-         * from any client process. The base state is guaranteed to match the state provided with a
-         * call to {@link #onStateChanged(int)} when there are no active requests from any process.
-         * <p>
-         * Guaranteed to be called once on registration of the callback with the initial value and
-         * then on every subsequent change in the non-override state.
-         *
-         * @param state the new base device state.
-         * @deprecated use {@link #onDeviceStateChanged(DeviceState)} and query for physical
-         * properties that are relevant to your needs.
-         * @hide
-         */
-        // TODO(b/325124054): Make non-default and remove deprecated callback methods.
-        @TestApi
-        @Deprecated
-        default void onBaseStateChanged(int state) {}
-
-        /**
-         * Called in response to device state changes.
-         * <p>
-         * Guaranteed to be called once on registration of the callback with the initial value and
-         * then on every subsequent change in device state.
-         *
-         * @param state the new device state.
-         * @deprecated use {@link #onDeviceStateChanged(DeviceState)}
-         * @hide
-         */
-        // TODO(b/325124054): Make non-default and remove deprecated callback methods.
-        @TestApi
-        @Deprecated
-        void onStateChanged(int state);
-
-        /**
          * Called in response to device state changes.
          * <p>
          * Guaranteed to be called once on registration of the callback with the initial value and
@@ -310,8 +244,7 @@
          *
          * @param state the new device state.
          */
-        // TODO(b/325124054): Make non-default and remove deprecated callback methods.
-        default void onDeviceStateChanged(@NonNull DeviceState state) {}
+        void onDeviceStateChanged(@NonNull DeviceState state);
     }
 
     /**
@@ -340,9 +273,6 @@
         }
 
         @Override
-        public final void onStateChanged(int state) {}
-
-        @Override
         public final void onDeviceStateChanged(@NonNull DeviceState deviceState) {
             final boolean folded;
             if (mFeatureFlags.deviceStatePropertyApi()) {
diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
index d6cc00d..0c84019 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
@@ -90,33 +90,6 @@
     }
 
     /**
-     * Returns the set of supported device states.
-     *
-     * @see DeviceStateManager#getSupportedStates()
-     */
-    // TODO(b/325124054): Remove unused methods when clients are migrated.
-    public int[] getSupportedStates() {
-        synchronized (mLock) {
-            final DeviceStateInfo currentInfo;
-            if (mLastReceivedInfo != null) {
-                // If we have mLastReceivedInfo a callback is registered for this instance and it
-                // is receiving the most recent info from the server. Use that info here.
-                currentInfo = mLastReceivedInfo;
-            } else {
-                // If mLastReceivedInfo is null there is no registered callback so we manually
-                // fetch the current info.
-                try {
-                    currentInfo = mDeviceStateManager.getDeviceStateInfo();
-                } catch (RemoteException ex) {
-                    throw ex.rethrowFromSystemServer();
-                }
-            }
-
-            return getSupportedStateIdentifiersLocked(currentInfo.supportedStates);
-        }
-    }
-
-    /**
      * Returns {@link List} of supported {@link DeviceState}s.
      *
      * @see DeviceStateManager#getSupportedDeviceStates()
@@ -264,14 +237,8 @@
             mCallbacks.add(wrapper);
 
             if (mLastReceivedInfo != null) {
-                // Copy the array to prevent the callback from modifying the internal state.
-                final int[] supportedStates = getSupportedStateIdentifiersLocked(
-                        mLastReceivedInfo.supportedStates);
-                wrapper.notifySupportedStatesChanged(supportedStates);
                 wrapper.notifySupportedDeviceStatesChanged(
                         List.copyOf(mLastReceivedInfo.supportedStates));
-                wrapper.notifyBaseStateChanged(mLastReceivedInfo.baseState.getIdentifier());
-                wrapper.notifyStateChanged(mLastReceivedInfo.currentState.getIdentifier());
                 wrapper.notifyDeviceStateChanged(mLastReceivedInfo.currentState);
             }
         }
@@ -330,15 +297,6 @@
         return -1;
     }
 
-    @GuardedBy("mLock")
-    private int[] getSupportedStateIdentifiersLocked(List<DeviceState> states) {
-        int[] identifiers = new int[states.size()];
-        for (int i = 0; i < states.size(); i++) {
-            identifiers[i] = states.get(i).getIdentifier();
-        }
-        return identifiers;
-    }
-
     @Nullable
     private IBinder findRequestTokenLocked(@NonNull DeviceStateRequest request) {
         for (int i = 0; i < mRequests.size(); i++) {
@@ -353,12 +311,10 @@
     private void handleDeviceStateInfoChanged(@NonNull DeviceStateInfo info) {
         ArrayList<DeviceStateCallbackWrapper> callbacks;
         DeviceStateInfo oldInfo;
-        int[] supportedStateIdentifiers;
         synchronized (mLock) {
             oldInfo = mLastReceivedInfo;
             mLastReceivedInfo = info;
             callbacks = new ArrayList<>(mCallbacks);
-            supportedStateIdentifiers = getSupportedStateIdentifiersLocked(info.supportedStates);
         }
 
         final int diff = oldInfo == null ? ~0 : info.diff(oldInfo);
@@ -366,18 +322,11 @@
             for (int i = 0; i < callbacks.size(); i++) {
                 callbacks.get(i).notifySupportedDeviceStatesChanged(
                         List.copyOf(info.supportedStates));
-                callbacks.get(i).notifySupportedStatesChanged(supportedStateIdentifiers);
-            }
-        }
-        if ((diff & DeviceStateInfo.CHANGED_BASE_STATE) > 0) {
-            for (int i = 0; i < callbacks.size(); i++) {
-                callbacks.get(i).notifyBaseStateChanged(info.baseState.getIdentifier());
             }
         }
         if ((diff & DeviceStateInfo.CHANGED_CURRENT_STATE) > 0) {
             for (int i = 0; i < callbacks.size(); i++) {
                 callbacks.get(i).notifyDeviceStateChanged(info.currentState);
-                callbacks.get(i).notifyStateChanged(info.currentState.getIdentifier());
             }
         }
     }
@@ -439,26 +388,11 @@
             mExecutor = executor;
         }
 
-        void notifySupportedStatesChanged(int[] newSupportedStates) {
-            mExecutor.execute(() ->
-                    mDeviceStateCallback.onSupportedStatesChanged(newSupportedStates));
-        }
-
         void notifySupportedDeviceStatesChanged(List<DeviceState> newSupportedDeviceStates) {
             mExecutor.execute(() ->
                     mDeviceStateCallback.onSupportedStatesChanged(newSupportedDeviceStates));
         }
 
-        void notifyBaseStateChanged(int newBaseState) {
-            execute("notifyBaseStateChanged",
-                    () -> mDeviceStateCallback.onBaseStateChanged(newBaseState));
-        }
-
-        void notifyStateChanged(int newDeviceState) {
-            execute("notifyStateChanged",
-                    () -> mDeviceStateCallback.onStateChanged(newDeviceState));
-        }
-
         void notifyDeviceStateChanged(DeviceState newDeviceState) {
             execute("notifyDeviceStateChanged",
                     () -> mDeviceStateCallback.onDeviceStateChanged(newDeviceState));
diff --git a/core/java/android/hardware/devicestate/DeviceStateRequest.java b/core/java/android/hardware/devicestate/DeviceStateRequest.java
index 893d765..7665e2f 100644
--- a/core/java/android/hardware/devicestate/DeviceStateRequest.java
+++ b/core/java/android/hardware/devicestate/DeviceStateRequest.java
@@ -115,8 +115,8 @@
          * requested state.
          * <p>
          * Guaranteed to be called after a call to
-         * {@link DeviceStateManager.DeviceStateCallback#onStateChanged(int)} with a state
-         * matching the requested state.
+         * {@link DeviceStateManager.DeviceStateCallback#onDeviceStateChanged(DeviceState)} with a
+         * state matching the requested state.
          */
         default void onRequestActivated(@NonNull DeviceStateRequest request) {}
 
@@ -124,7 +124,7 @@
          * Called to indicate the request has been temporarily suspended.
          * <p>
          * Guaranteed to be called before a call to
-         * {@link DeviceStateManager.DeviceStateCallback#onStateChanged(int)}.
+         * {@link DeviceStateManager.DeviceStateCallback#onDeviceStateChanged(DeviceState)}.
          */
         default void onRequestSuspended(@NonNull DeviceStateRequest request) {}
 
@@ -134,7 +134,7 @@
          * DeviceStateRequest.Callback)}.
          * <p>
          * Guaranteed to be called before a call to
-         * {@link DeviceStateManager.DeviceStateCallback#onStateChanged(int)}.
+         * {@link DeviceStateManager.DeviceStateCallback#onDeviceStateChanged(DeviceState)}.
          * <p>
          * Note: A call to {@link #onRequestSuspended(DeviceStateRequest)} is not guaranteed to
          * occur before this method.
diff --git a/core/java/android/hardware/devicestate/DeviceStateUtil.java b/core/java/android/hardware/devicestate/DeviceStateUtil.java
new file mode 100644
index 0000000..627e740
--- /dev/null
+++ b/core/java/android/hardware/devicestate/DeviceStateUtil.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.devicestate;
+
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
+
+import android.annotation.NonNull;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Utilities for {@link DeviceStateManager}.
+ * @hide
+ */
+public class DeviceStateUtil {
+    private DeviceStateUtil() { }
+
+    /**
+     * Returns the state identifier of the {@link DeviceState} that matches the
+     * {@code currentState}s physical properties. This will return the identifier of the
+     * {@link DeviceState} that matches the devices physical configuration.
+     *
+     * Returns {@link INVALID_DEVICE_STATE_IDENTIFIER} if there is no {@link DeviceState} in the
+     * provided list of {@code supportedStates} that matches.
+     * @hide
+     */
+    public static int calculateBaseStateIdentifier(@NonNull DeviceState currentState,
+            @NonNull List<DeviceState> supportedStates) {
+        DeviceState.Configuration stateConfiguration = currentState.getConfiguration();
+        for (int i = 0; i < supportedStates.size(); i++) {
+            DeviceState stateToCompare = supportedStates.get(i);
+            if (stateToCompare.getConfiguration().getPhysicalProperties().isEmpty()) {
+                continue;
+            }
+            if (isDeviceStateMatchingPhysicalProperties(stateConfiguration.getPhysicalProperties(),
+                    supportedStates.get(i))) {
+                return supportedStates.get(i).getIdentifier();
+            }
+        }
+        return INVALID_DEVICE_STATE_IDENTIFIER;
+    }
+
+    /**
+     * Returns if the physical properties provided, matches the same physical properties on the
+     * provided {@link DeviceState}.
+     */
+    private static boolean isDeviceStateMatchingPhysicalProperties(
+            Set<@DeviceState.PhysicalDeviceStateProperties Integer> physicalProperties,
+            DeviceState state) {
+        Iterator<@DeviceState.PhysicalDeviceStateProperties Integer> iterator =
+                physicalProperties.iterator();
+        while (iterator.hasNext()) {
+            if (!state.hasProperty(iterator.next())) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index eb26a76..4894fb1 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -1653,6 +1653,22 @@
     }
 
     /**
+     * Allows internal application to restrict display modes to specified modeIds
+     *
+     * @param displayId display that restrictions will be applied to
+     * @param modeIds allowed mode ids
+     *
+     * @hide
+     */
+    @RequiresPermission("android.permission.RESTRICT_DISPLAY_MODES")
+    public void requestDisplayModes(int displayId, @Nullable int[] modeIds) {
+        if (modeIds != null && modeIds.length == 0) {
+            throw new IllegalArgumentException("requestDisplayModes: modesIds can't be empty");
+        }
+        mGlobal.requestDisplayModes(displayId, modeIds);
+    }
+
+    /**
      * Listens for changes in available display devices.
      */
     public interface DisplayListener {
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 75f0ceb..3d7b714 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -38,6 +38,7 @@
 import android.hardware.graphics.common.DisplayDecorationSupport;
 import android.media.projection.IMediaProjection;
 import android.media.projection.MediaProjection;
+import android.os.Binder;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.IBinder;
@@ -138,6 +139,8 @@
 
     private int mWifiDisplayScanNestCount;
 
+    private final Binder mToken = new Binder();
+
     @VisibleForTesting
     public DisplayManagerGlobal(IDisplayManager dm) {
         mDm = dm;
@@ -1182,6 +1185,20 @@
         }
     }
 
+    /**
+     * Sets allowed display mode ids
+     *
+     * @hide
+     */
+    @RequiresPermission("android.permission.RESTRICT_DISPLAY_MODES")
+    public void requestDisplayModes(int displayId, @Nullable int[] modeIds) {
+        try {
+            mDm.requestDisplayModes(mToken, displayId, modeIds);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
     private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub {
         @Override
         public void onDisplayEvent(int displayId, @DisplayEvent int event) {
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 83de4e4..70efc6f 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -235,4 +235,8 @@
     // Disable a connected display that is enabled.
     @EnforcePermission("MANAGE_DISPLAYS")
     void disableConnectedDisplay(int displayId);
+
+    // Restricts display modes to specified modeIds.
+    @EnforcePermission("RESTRICT_DISPLAY_MODES")
+    void requestDisplayModes(in IBinder token, int displayId, in @nullable int[] modeIds);
 }
diff --git a/core/java/android/hardware/location/NanoAppMessage.java b/core/java/android/hardware/location/NanoAppMessage.java
index 48aa1bd..905caf0 100644
--- a/core/java/android/hardware/location/NanoAppMessage.java
+++ b/core/java/android/hardware/location/NanoAppMessage.java
@@ -164,16 +164,18 @@
 
     /**
      * Sets the isReliable field of the message
+     *
+     * @hide
      */
-    @FlaggedApi(Flags.FLAG_RELIABLE_MESSAGE)
     public void setIsReliable(boolean isReliable) {
         mIsReliable = isReliable;
     }
 
     /**
      * Sets the message sequence number of the message
+     *
+     * @hide
      */
-    @FlaggedApi(Flags.FLAG_RELIABLE_MESSAGE)
     public void setMessageSequenceNumber(int messageSequenceNumber) {
         mMessageSequenceNumber = messageSequenceNumber;
     }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index fdaa0b4..318c291 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -3189,6 +3189,7 @@
             conditional = true)
     @UserHandleAware
     public boolean canAddPrivateProfile() {
+        if (!android.multiuser.Flags.enablePrivateSpaceFeatures()) return false;
         if (android.multiuser.Flags.blockPrivateSpaceCreation()) {
             try {
                 return mService.canAddPrivateProfile(mUserId);
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 943014c..375d729 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -38,6 +38,7 @@
     bug: "288119641"
 }
 
+# This flag guards the private space feature, its APIs, and some of the feature implementations. The flag android.multiuser.Flags.enable_private_space_features exclusively guards all the implementations.
 flag {
     name: "allow_private_profile"
     namespace: "profile_experiences"
diff --git a/core/java/android/service/media/OWNERS b/core/java/android/service/media/OWNERS
deleted file mode 100644
index 916fc36..0000000
--- a/core/java/android/service/media/OWNERS
+++ /dev/null
@@ -1,8 +0,0 @@
-# Bug component: 137631
-
-hdmoon@google.com
-insun@google.com
-jaewan@google.com
-jinpark@google.com
-klhyun@google.com
-gyumin@google.com
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..6815440 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;
@@ -226,15 +222,18 @@
 
     /**
      * Invoked by the {@link OnDeviceIntelligenceService} inorder to send updates to the inference
-     * service if there is a state change to be performed.
+     * service if there is a state change to be performed. State change could be config updates,
+     * performing initialization or cleanup tasks in the remote inference service.
+     * The Bundle passed in here is expected to be read-only and will be rejected if it has any
+     * writable fields as detailed under {@link InferenceParams}.
      *
      * @param processingState  the updated state to be applied.
      * @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 +253,7 @@
                         public void onFailure(int errorCode, String errorMessage) {
                             Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
                                     () -> statusReceiver.onError(
-                                            new OnDeviceUpdateProcessingException(
+                                            new OnDeviceIntelligenceException(
                                                     errorCode, errorMessage))));
                         }
                     });
@@ -265,7 +264,7 @@
     }
 
     private OutcomeReceiver<Feature,
-            OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> wrapFeatureCallback(
+            OnDeviceIntelligenceException> wrapFeatureCallback(
             IFeatureCallback featureCallback) {
         return new OutcomeReceiver<>() {
             @Override
@@ -279,7 +278,7 @@
 
             @Override
             public void onError(
-                    @NonNull OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException exception) {
+                    @NonNull OnDeviceIntelligenceException exception) {
                 try {
                     featureCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
                             exception.getErrorParams());
@@ -291,7 +290,7 @@
     }
 
     private OutcomeReceiver<List<Feature>,
-            OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> wrapListFeaturesCallback(
+            OnDeviceIntelligenceException> wrapListFeaturesCallback(
             IListFeaturesCallback listFeaturesCallback) {
         return new OutcomeReceiver<>() {
             @Override
@@ -305,7 +304,7 @@
 
             @Override
             public void onError(
-                    @NonNull OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException exception) {
+                    @NonNull OnDeviceIntelligenceException exception) {
                 try {
                     listFeaturesCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
                             exception.getErrorParams());
@@ -317,7 +316,7 @@
     }
 
     private OutcomeReceiver<FeatureDetails,
-            OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> wrapFeatureDetailsCallback(
+            OnDeviceIntelligenceException> wrapFeatureDetailsCallback(
             IFeatureDetailsCallback featureStatusCallback) {
         return new OutcomeReceiver<>() {
             @Override
@@ -331,7 +330,7 @@
 
             @Override
             public void onError(
-                    @NonNull OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException exception) {
+                    @NonNull OnDeviceIntelligenceException exception) {
                 try {
                     featureStatusCallback.onFailure(exception.getErrorCode(),
                             exception.getMessage(), exception.getErrorParams());
@@ -444,7 +443,7 @@
      */
     public abstract void onGetFeatureDetails(int callerUid, @NonNull Feature feature,
             @NonNull OutcomeReceiver<FeatureDetails,
-                    OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureDetailsCallback);
+                    OnDeviceIntelligenceException> featureDetailsCallback);
 
 
     /**
@@ -455,7 +454,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 +464,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 +473,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/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index a098e4d..4f5b51d 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -2631,7 +2631,10 @@
      * Threshold values that are sent with
      * {@link Transaction#setTrustedPresentationCallback(SurfaceControl,
      * TrustedPresentationThresholds, Executor, Consumer)}
+     *
+     * @deprecated Use {@link android.window.TrustedPresentationThresholds} instead.
      */
+    @Deprecated
     public static final class TrustedPresentationThresholds {
         private final float mMinAlpha;
         private final float mMinFractionRendered;
@@ -4451,8 +4454,7 @@
          *   <tr><td>o</td><td>x</td><td>x</td><td>x</td></tr>
          * </table>
          * </blockquote>
-         *
-         *<p>
+         * <p>
          * We first start by computing fr=xscale*yscale=0.9*0.9=0.81, indicating
          * that "81%" of the pixels were rendered. This corresponds to what was 100
          * pixels being displayed in 81 pixels. This is somewhat of an abuse of
@@ -4469,6 +4471,7 @@
          * be somewhat arbitrary, and so there are some somewhat arbitrary decisions in
          * this API as well.
          * <p>
+         *
          * @param sc         The {@link SurfaceControl} to set the callback on
          * @param thresholds The {@link TrustedPresentationThresholds} that will specify when the to
          *                   invoke the callback.
@@ -4477,7 +4480,11 @@
          *                   exited the threshold.
          * @return This transaction
          * @see TrustedPresentationThresholds
+         * @deprecated Use
+         * {@link WindowManager#registerTrustedPresentationListener(IBinder,
+         * android.window.TrustedPresentationThresholds, Executor, Consumer)} instead.
          */
+        @Deprecated
         @NonNull
         public Transaction setTrustedPresentationCallback(@NonNull SurfaceControl sc,
                 @NonNull TrustedPresentationThresholds thresholds, @NonNull Executor executor,
@@ -4506,7 +4513,10 @@
          *
          * @param sc The SurfaceControl that the callback should be cleared from
          * @return This transaction
+         * @deprecated Use {@link WindowManager#unregisterTrustedPresentationListener(Consumer)}
+         * instead.
          */
+        @Deprecated
         @NonNull
         public Transaction clearTrustedPresentationCallback(@NonNull SurfaceControl sc) {
             checkPreconditions(sc);
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 6f757df..04ec4d0 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -583,13 +583,16 @@
      * associated host {@link InputTransferToken}.
      *
      * @return Whether the touch stream was transferred.
+     * @deprecated Use {@link WindowManager#transferTouchGesture(InputTransferToken,
+     * InputTransferToken)} instead.
      */
+    @Deprecated
     public boolean transferTouchGestureToHost() {
         if (mViewRoot == null) {
             return false;
         }
-        final WindowManager wm =
-                (WindowManager) mViewRoot.mContext.getSystemService(Context.WINDOW_SERVICE);
+        final WindowManager wm = (WindowManager) mViewRoot.mContext.getSystemService(
+                Context.WINDOW_SERVICE);
         InputTransferToken embeddedToken = getInputTransferToken();
         InputTransferToken hostToken = mWm.mHostInputTransferToken;
         if (embeddedToken == null || hostToken == null) {
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index d494e28..c95d6ff 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1946,7 +1946,9 @@
      * be passed from the host process to the client process.
      *
      * @return The token
+     * @deprecated Use {@link AttachedSurfaceControl#getInputTransferToken()} instead.
      */
+    @Deprecated
     public @Nullable IBinder getHostToken() {
         final ViewRootImpl viewRoot = getViewRootImpl();
         if (viewRoot == null) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index a5ff48f..0ac8936 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2372,6 +2372,12 @@
     protected static final int[] PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
 
     /**
+     * This indicates that the frame rate category was chosen for an unknown reason.
+     * @hide
+     */
+    public static final int FRAME_RATE_CATEGORY_REASON_UNKNOWN = 0x0000_0000;
+
+    /**
      * This indicates that the frame rate category was chosen because it was a small area update.
      * @hide
      */
@@ -2402,9 +2408,24 @@
      */
     public static final int FRAME_RATE_CATEGORY_REASON_INVALID = 0x0500_0000;
 
+    /**
+     * This indicates that the frame rate category was chosen because the view has a velocity
+     * @hide
+     */
+    public static final int FRAME_RATE_CATEGORY_REASON_VELOCITY = 0x0600_0000;
+
+    /**
+     * This indicates that the frame rate category was chosen because it is idle.
+     * @hide
+     */
+    public static final int FRAME_RATE_CATEGORY_REASON_IDLE = 0x0700_0000;
+
     private static final int FRAME_RATE_CATEGORY_REASON_MASK = 0xFFFF_0000;
 
-    private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
+    /**
+     * @hide
+     */
+    protected static boolean sToolkitSetFrameRateReadOnlyFlagValue;
     private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
     // Used to set frame rate compatibility.
     @Surface.FrameRateCompatibility int mFrameRateCompatibility =
@@ -33778,6 +33799,10 @@
                     return;
                 }
             }
+            if (sToolkitMetricsForFrameRateDecisionFlagValue) {
+                float sizePercentage = getSizePercentage();
+                viewRootImpl.recordViewPercentage(sizePercentage);
+            }
             int frameRateCategory;
             if (Float.isNaN(mPreferredFrameRate)) {
                 frameRateCategory = calculateFrameRateCategory(width, height);
@@ -33806,11 +33831,8 @@
             }
 
             int category = frameRateCategory & ~FRAME_RATE_CATEGORY_REASON_MASK;
-            if (sToolkitMetricsForFrameRateDecisionFlagValue) {
-                int reason = frameRateCategory & FRAME_RATE_CATEGORY_REASON_MASK;
-                viewRootImpl.recordCategory(category, reason, this);
-            }
-            viewRootImpl.votePreferredFrameRateCategory(category);
+            int reason = frameRateCategory & FRAME_RATE_CATEGORY_REASON_MASK;
+            viewRootImpl.votePreferredFrameRateCategory(category, reason, this);
             mLastFrameRateCategory = frameRateCategory;
         }
     }
@@ -33906,9 +33928,9 @@
     }
 
     /**
-     * This function is mainly used for migrating infrequent layer lagic
+     * This function is mainly used for migrating infrequent layer logic
      * from SurfaceFlinger to Toolkit.
-     * The infrequent layter logic includes:
+     * The infrequent layer logic includes:
      * - NORMAL for infrequent update: FT2-FT1 > 100 && FT3-FT2 > 100.
      * - HIGH/NORMAL based on size for frequent update: (FT3-FT2) + (FT2 - FT1) < 100.
      * - otherwise, use the previous category value.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 6f178bb..8c3cf5f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -32,11 +32,14 @@
 import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
 import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
 import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_UNKNOWN;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_IDLE;
 import static android.view.View.FRAME_RATE_CATEGORY_REASON_INTERMITTENT;
 import static android.view.View.FRAME_RATE_CATEGORY_REASON_INVALID;
 import static android.view.View.FRAME_RATE_CATEGORY_REASON_LARGE;
 import static android.view.View.FRAME_RATE_CATEGORY_REASON_REQUESTED;
 import static android.view.View.FRAME_RATE_CATEGORY_REASON_SMALL;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_VELOCITY;
 import static android.view.View.PFLAG_DRAW_ANIMATION;
 import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
 import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
@@ -847,6 +850,8 @@
     private boolean mInsetsAnimationRunning;
 
     private long mPreviousFrameDrawnTime = -1;
+    // The largest view size percentage to the display size. Used on trace to collect metric.
+    private float mLargestChildPercentage = 0.0f;
     // The reason the category was changed.
     private int mFrameRateCategoryChangeReason = 0;
     private String mFrameRateCategoryView;
@@ -4854,6 +4859,10 @@
         long fps = NANOS_PER_SEC / timeDiff;
         Trace.setCounter(mFpsTraceName, fps);
         mPreviousFrameDrawnTime = expectedDrawnTime;
+
+        long percentage = (long) (mLargestChildPercentage * 100);
+        Trace.setCounter(mLargestViewTraceName, percentage);
+        mLargestChildPercentage = 0.0f;
     }
 
     private void reportDrawFinished(@Nullable Transaction t, int seqId) {
@@ -6540,6 +6549,8 @@
                 case MSG_CHECK_INVALIDATION_IDLE:
                     if (!mHasInvalidation && !mIsFrameRateBoosting && !mIsTouchBoosting) {
                         mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+                        mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_IDLE;
+                        mFrameRateCategoryView = null;
                         setPreferredFrameRateCategory(mPreferredFrameRateCategory);
                         mHasIdledMessage = false;
                     } else {
@@ -12367,24 +12378,37 @@
                 || (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE
                         && mPreferredFrameRate > 0)) {
             frameRateCategory = FRAME_RATE_CATEGORY_HIGH;
+            if (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE) {
+                // We've received a velocity, so we'll let the velocity control the
+                // frame rate unless we receive additional motion events.
+                mIsTouchBoosting = false;
+                mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_VELOCITY;
+                mFrameRateCategoryView = null;
+            } else {
+                mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_UNKNOWN;
+            }
         }
 
         try {
             if (mLastPreferredFrameRateCategory != frameRateCategory) {
                 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-                    String reason = "none";
-                    switch (mFrameRateCategoryChangeReason) {
-                        case FRAME_RATE_CATEGORY_REASON_INTERMITTENT -> reason = "intermittent";
-                        case FRAME_RATE_CATEGORY_REASON_SMALL -> reason = "small";
-                        case FRAME_RATE_CATEGORY_REASON_LARGE -> reason = "large";
-                        case FRAME_RATE_CATEGORY_REASON_REQUESTED -> reason = "requested";
-                        case FRAME_RATE_CATEGORY_REASON_INVALID -> reason = "invalid frame rate";
-                    }
-                    String sourceView = mFrameRateCategoryView == null ? "No View Given"
+                    String reason = reasonToString(mFrameRateCategoryChangeReason);
+                    String sourceView = mFrameRateCategoryView == null ? "-"
                             : mFrameRateCategoryView;
+                    if (preferredFrameRateCategory == FRAME_RATE_CATEGORY_HIGH_HINT) {
+                        reason = "touch boost";
+                        sourceView = "-";
+                    } else if (categoryFromConflictedFrameRates == frameRateCategory
+                            && frameRateCategory != preferredFrameRateCategory
+                            && mIsFrameRateConflicted
+                    ) {
+                        reason = "conflict";
+                        sourceView = "-";
+                    }
+                    String category = categoryToString(frameRateCategory);
                     Trace.traceBegin(
                             Trace.TRACE_TAG_VIEW, "ViewRootImpl#setFrameRateCategory "
-                                    + frameRateCategory + ", reason " + reason + ", "
+                                    + category + ", reason " + reason + ", "
                                     + sourceView);
                 }
                 mFrameRateTransaction.setFrameRateCategory(mSurfaceControl,
@@ -12398,6 +12422,35 @@
         }
     }
 
+    private static String categoryToString(int frameRateCategory) {
+        String category;
+        switch (frameRateCategory) {
+            case FRAME_RATE_CATEGORY_NO_PREFERENCE -> category = "no preference";
+            case FRAME_RATE_CATEGORY_LOW -> category = "low";
+            case FRAME_RATE_CATEGORY_NORMAL -> category = "normal";
+            case FRAME_RATE_CATEGORY_HIGH_HINT -> category = "high hint";
+            case FRAME_RATE_CATEGORY_HIGH -> category = "high";
+            default -> category = "default";
+        }
+        return category;
+    }
+
+    private static String reasonToString(int reason) {
+        String str;
+        switch (reason) {
+            case FRAME_RATE_CATEGORY_REASON_INTERMITTENT -> str = "intermittent";
+            case FRAME_RATE_CATEGORY_REASON_SMALL -> str = "small";
+            case FRAME_RATE_CATEGORY_REASON_LARGE -> str = "large";
+            case FRAME_RATE_CATEGORY_REASON_REQUESTED -> str = "requested";
+            case FRAME_RATE_CATEGORY_REASON_INVALID -> str = "invalid frame rate";
+            case FRAME_RATE_CATEGORY_REASON_VELOCITY -> str = "velocity";
+            case FRAME_RATE_CATEGORY_REASON_IDLE -> str = "idle";
+            case FRAME_RATE_CATEGORY_REASON_UNKNOWN -> str = "unknown";
+            default -> str = String.valueOf(reason);
+        }
+        return str;
+    }
+
     private void setPreferredFrameRate(float preferredFrameRate) {
         if (!shouldSetFrameRate() || (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE
                 && preferredFrameRate > 0)) {
@@ -12417,17 +12470,6 @@
                     mFrameRateCompatibility).applyAsyncUnsafe();
                 mLastPreferredFrameRate = preferredFrameRate;
             }
-            if (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE && mIsTouchBoosting) {
-                // We've received a velocity, so we'll let the velocity control the
-                // frame rate unless we receive additional motion events.
-                mIsTouchBoosting = false;
-                if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-                    Trace.instant(
-                            Trace.TRACE_TAG_VIEW,
-                            "ViewRootImpl#setFrameRate velocity used, no touch boost on next frame"
-                    );
-                }
-            }
         } catch (Exception e) {
             Log.e(mTag, "Unable to set frame rate", e);
         } finally {
@@ -12468,7 +12510,7 @@
      * @param frameRateCategory the preferred frame rate category of a View
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
-    public void votePreferredFrameRateCategory(int frameRateCategory) {
+    public void votePreferredFrameRateCategory(int frameRateCategory, int reason, View view) {
         if (frameRateCategory == FRAME_RATE_CATEGORY_HIGH) {
             mFrameRateCategoryHighCount = FRAME_RATE_CATEGORY_COUNT;
         } else if (frameRateCategory == FRAME_RATE_CATEGORY_HIGH_HINT) {
@@ -12479,6 +12521,7 @@
             mFrameRateCategoryLowCount = FRAME_RATE_CATEGORY_COUNT;
         }
 
+        int oldCategory = mPreferredFrameRateCategory;
         if (mFrameRateCategoryHighCount > 0) {
             mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_HIGH;
         } else if (mFrameRateCategoryHighHintCount > 0) {
@@ -12490,6 +12533,13 @@
         }
         mHasInvalidation = true;
         checkIdleness();
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)
+                && mPreferredFrameRateCategory != oldCategory
+                && mPreferredFrameRateCategory == frameRateCategory
+        ) {
+            mFrameRateCategoryChangeReason = reason;
+            mFrameRateCategoryView = view.getClass().getSimpleName();
+        }
     }
 
     /**
@@ -12617,12 +12667,10 @@
         mWindowlessBackKeyCallback = callback;
     }
 
-    void recordCategory(int category, int reason, View view) {
+    void recordViewPercentage(float percentage) {
         if (!Trace.isEnabled()) return;
-        if (category > mPreferredFrameRateCategory) {
-            mFrameRateCategoryChangeReason = reason;
-            mFrameRateCategoryView = view.getClass().getSimpleName();
-        }
+        // Record the largest view of percentage to the display size.
+        mLargestChildPercentage = Math.max(percentage, mLargestChildPercentage);
     }
 
     /**
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 64846d0..c127a43 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1571,8 +1571,9 @@
     /**
      * Value applicable for the {@link #PROPERTY_COMPAT_ALLOW_SMALL_COVER_SCREEN} property to
      * provide a signal to the system that an application or its specific activities explicitly
-     * opt into being displayed on small foldable device cover screens that measure at least 1.5
-     * inches for the shorter dimension and at least 2.4 inches for the longer dimension.
+     * opt into being displayed on small cover screens on flippable style foldable devices that
+     * measure at least 1.5 inches up to 2.2 inches for the shorter dimension and at least 2.4
+     * inches up to 3.4 inches for the longer dimension
      */
     @CompatSmallScreenPolicy
     @FlaggedApi(Flags.FLAG_COVER_DISPLAY_OPT_IN)
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index e215950..614df7c 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -142,7 +142,7 @@
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)")
     void attachAccessibilityOverlayToDisplay(int displayId, in SurfaceControl surfaceControl);
 
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE)")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.STATUS_BAR_SERVICE,android.Manifest.permission.MANAGE_ACCESSIBILITY})")
     oneway void notifyQuickSettingsTilesChanged(int userId, in List<ComponentName> tileComponentNames);
 
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)")
diff --git a/core/java/android/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..1d9eb71 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();
     }
 
@@ -3056,19 +3056,17 @@
     }
 
     @GuardedBy("mLock")
-    private void handleFailedIdsLocked(ArrayList<AutofillId> failedIds) {
-        if (failedIds != null && !failedIds.isEmpty()) {
-            if (sVerbose) {
-                Log.v(TAG, "autofill(): total failed views: " + failedIds);
-            }
-            try {
-                mService.setAutofillFailure(mSessionId, failedIds, mContext.getUserId());
-            } catch (RemoteException e) {
-                // In theory, we could ignore this error since it's not a big deal, but
-                // in reality, we rather crash the app anyways, as the failure could be
-                // a consequence of something going wrong on the server side...
-                throw e.rethrowFromSystemServer();
-            }
+    private void handleFailedIdsLocked(@NonNull ArrayList<AutofillId> failedIds) {
+        if (!failedIds.isEmpty() && sVerbose) {
+            Log.v(TAG, "autofill(): total failed views: " + failedIds);
+        }
+        try {
+            mService.setAutofillFailure(mSessionId, failedIds, mContext.getUserId());
+        } catch (RemoteException e) {
+            // In theory, we could ignore this error since it's not a big deal, but
+            // in reality, we rather crash the app anyways, as the failure could be
+            // a consequence of something going wrong on the server side...
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -3090,7 +3088,7 @@
             final View[] views = client.autofillClientFindViewsByAutofillIdTraversal(
                     Helper.toArray(ids));
 
-            ArrayList<AutofillId> failedIds = null;
+            ArrayList<AutofillId> failedIds = new ArrayList<>();
 
             for (int i = 0; i < itemCount; i++) {
                 final AutofillId id = ids.get(i);
@@ -3101,9 +3099,6 @@
                     // the service; this is fine, but we need to update the view status in the
                     // server side so it can be triggered again.
                     Log.d(TAG, "autofill(): no View with id " + id);
-                    if (failedIds == null) {
-                        failedIds = new ArrayList<>();
-                    }
                     failedIds.add(id);
                     continue;
                 }
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/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/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index c769518..6bd273b 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -659,6 +659,7 @@
 
     private boolean privateSpaceFlagsEnabled() {
         return android.os.Flags.allowPrivateProfile()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures()
                 && android.multiuser.Flags.enablePrivateSpaceIntentRedirection();
     }
 
diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java
index 40a437f..9dec102 100644
--- a/core/java/com/android/internal/policy/TransitionAnimation.java
+++ b/core/java/com/android/internal/policy/TransitionAnimation.java
@@ -129,7 +129,6 @@
     private final int mDefaultWindowAnimationStyleResId;
 
     private final boolean mDebug;
-    private final boolean mGridLayoutRecentsEnabled;
     private final boolean mLowRamRecentsEnabled;
 
     public TransitionAnimation(Context context, boolean debug, String tag) {
@@ -166,7 +165,6 @@
         mConfigShortAnimTime = context.getResources().getInteger(
                 com.android.internal.R.integer.config_shortAnimTime);
 
-        mGridLayoutRecentsEnabled = SystemProperties.getBoolean("ro.recents.grid", false);
         mLowRamRecentsEnabled = ActivityManager.isLowRamDeviceStatic();
 
         final TypedArray windowStyle = mContext.getTheme().obtainStyledAttributes(
@@ -768,10 +766,8 @@
                         // We scale the width and clip to the top/left square
                         float scale =
                                 thumbWidth / (appWidth - contentInsets.left - contentInsets.right);
-                        if (!mGridLayoutRecentsEnabled) {
-                            int unscaledThumbHeight = (int) (thumbHeight / scale);
-                            mTmpFromClipRect.bottom = mTmpFromClipRect.top + unscaledThumbHeight;
-                        }
+                        int unscaledThumbHeight = (int) (thumbHeight / scale);
+                        mTmpFromClipRect.bottom = mTmpFromClipRect.top + unscaledThumbHeight;
 
                         Animation scaleAnim = new ScaleAnimation(
                                 scaleUp ? scale : 1, scaleUp ? 1 : scale,
@@ -887,12 +883,6 @@
             toY = appRect.height() / 2 * (1 - 1 / scaleW) + appRect.top;
             pivotX = mTmpRect.width() / 2;
             pivotY = appRect.height() / 2 / scaleW;
-            if (mGridLayoutRecentsEnabled) {
-                // In the grid layout, the header is displayed above the thumbnail instead of
-                // overlapping it.
-                fromY -= thumbHeightI;
-                toY -= thumbHeightI * scaleW;
-            }
         } else {
             pivotX = 0;
             pivotY = 0;
@@ -936,10 +926,7 @@
             // This AnimationSet uses the Interpolators assigned above.
             AnimationSet set = new AnimationSet(false);
             set.addAnimation(scale);
-            if (!mGridLayoutRecentsEnabled) {
-                // In the grid layout, the header should be shown for the whole animation.
-                set.addAnimation(alpha);
-            }
+            set.addAnimation(alpha);
             set.addAnimation(translate);
             set.addAnimation(clipAnim);
             a = set;
@@ -958,10 +945,7 @@
             // This AnimationSet uses the Interpolators assigned above.
             AnimationSet set = new AnimationSet(false);
             set.addAnimation(scale);
-            if (!mGridLayoutRecentsEnabled) {
-                // In the grid layout, the header should be shown for the whole animation.
-                set.addAnimation(alpha);
-            }
+            set.addAnimation(alpha);
             set.addAnimation(translate);
             a = set;
 
@@ -1081,8 +1065,7 @@
      * @return whether the transition should show the thumbnail being scaled down.
      */
     private boolean shouldScaleDownThumbnailTransition(int orientation) {
-        return mGridLayoutRecentsEnabled
-                || orientation == Configuration.ORIENTATION_PORTRAIT;
+        return orientation == Configuration.ORIENTATION_PORTRAIT;
     }
 
     private static int updateToTranslucentAnimIfNeeded(int anim, @TransitionOldType int transit) {
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index a2efbd2..a22232a 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -376,6 +376,12 @@
     void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop);
 
     /**
+     * Set the split screen focus to the left / top app or the right / bottom app based on
+     * {@param leftOrTop}.
+     */
+    void setSplitscreenFocus(boolean leftOrTop);
+
+    /**
      * Shows the media output switcher dialog.
      *
      * @param packageName of the session for which the output switcher is shown.
diff --git a/core/java/com/android/internal/widget/CachingIconView.java b/core/java/com/android/internal/widget/CachingIconView.java
index 8ddd4ff..d67a630 100644
--- a/core/java/com/android/internal/widget/CachingIconView.java
+++ b/core/java/com/android/internal/widget/CachingIconView.java
@@ -113,7 +113,7 @@
     }
 
     @Nullable
-    private Drawable loadSizeRestrictedIcon(@Nullable Icon icon) {
+    Drawable loadSizeRestrictedIcon(@Nullable Icon icon) {
         return LocalImageResolver.resolveImage(icon, getContext(), mMaxDrawableWidth,
                 mMaxDrawableHeight);
     }
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 06835f0..6d5a96a 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -37,7 +37,6 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
 import android.graphics.drawable.Icon;
-import android.net.Uri;
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.text.Spannable;
@@ -1216,7 +1215,7 @@
             return new ConversationHeaderData(
                     conversationText,
                     new OneToOneConversationAvatarData(
-                            resolveAvatarImage(conversationIcon)));
+                            resolveAvatarImageForOneToOne(conversationIcon)));
         }
 
         final List<List<Notification.MessagingStyle.Message>> groupMessages = new ArrayList<>();
@@ -1283,18 +1282,29 @@
 
         return new ConversationHeaderData(
                 conversationText,
-                new GroupConversationAvatarData(resolveAvatarImage(lastIcon),
-                        resolveAvatarImage(secondLastIcon)));
+                new GroupConversationAvatarData(resolveAvatarImageForFacePile(lastIcon),
+                        resolveAvatarImageForFacePile(secondLastIcon)));
     }
 
     /**
-     * {@link ImageResolver#loadImage(Uri)} accepts Uri to load images. However Conversation Avatars
-     * are received as Icon. So, we can't make use of ImageResolver.
+     * One To One Conversation Avatars is loaded by CachingIconView(conversation icon view).
      */
     @Nullable
-    private Drawable resolveAvatarImage(Icon conversationIcon) {
+    private Drawable resolveAvatarImageForOneToOne(Icon conversationIcon) {
         try {
-            return LocalImageResolver.resolveImage(conversationIcon, getContext());
+            return mConversationIconView.loadSizeRestrictedIcon(conversationIcon);
+        } catch (Exception ex) {
+            return null;
+        }
+    }
+
+    /**
+     * Group Avatar drawables are loaded by Icon.
+     */
+    @Nullable
+    private Drawable resolveAvatarImageForFacePile(Icon conversationIcon) {
+        try {
+            return conversationIcon.loadDrawable(getContext());
         } catch (Exception ex) {
             return null;
         }
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index a0dc94f..ac961ee 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -266,6 +266,7 @@
                 "fd_utils.cpp",
                 "android_hardware_input_InputWindowHandle.cpp",
                 "android_hardware_input_InputApplicationHandle.cpp",
+                "android_window_InputTransferToken.cpp",
                 "android_window_WindowInfosListener.cpp",
                 "android_window_ScreenCapture.cpp",
                 "jni_common.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index aa63f4f..9bbd191 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -221,6 +221,7 @@
 extern int register_android_tracing_PerfettoDataSource(JNIEnv* env);
 extern int register_android_tracing_PerfettoDataSourceInstance(JNIEnv* env);
 extern int register_android_tracing_PerfettoProducer(JNIEnv* env);
+extern int register_android_window_InputTransferToken(JNIEnv* env);
 
 // Namespace for Android Runtime flags applied during boot time.
 static const char* RUNTIME_NATIVE_BOOT_NAMESPACE = "runtime_native_boot";
@@ -1678,6 +1679,7 @@
         REG_JNI(register_android_tracing_PerfettoDataSource),
         REG_JNI(register_android_tracing_PerfettoDataSourceInstance),
         REG_JNI(register_android_tracing_PerfettoProducer),
+        REG_JNI(register_android_window_InputTransferToken),
 };
 
 /*
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 07cbaad..3a1e883 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -19,24 +19,23 @@
 
 //#define LOG_NDEBUG 0
 
-#include <inttypes.h>
-
-#include <nativehelper/JNIHelp.h>
-
 #include <android-base/stringprintf.h>
 #include <android_runtime/AndroidRuntime.h>
+#include <input/InputConsumer.h>
 #include <input/InputTransport.h>
+#include <inttypes.h>
 #include <log/log.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
 #include <utils/Looper.h>
+
 #include <variant>
 #include <vector>
+
 #include "android_os_MessageQueue.h"
 #include "android_view_InputChannel.h"
 #include "android_view_KeyEvent.h"
 #include "android_view_MotionEvent.h"
-
-#include <nativehelper/ScopedLocalRef.h>
-
 #include "core_jni_helpers.h"
 
 namespace android {
diff --git a/core/jni/android_window_InputTransferToken.cpp b/core/jni/android_window_InputTransferToken.cpp
new file mode 100644
index 0000000..60568e30
--- /dev/null
+++ b/core/jni/android_window_InputTransferToken.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputTransferToken"
+
+#include <android_runtime/android_window_InputTransferToken.h>
+#include <gui/InputTransferToken.h>
+#include <nativehelper/JNIHelp.h>
+
+#include "android_os_Parcel.h"
+#include "android_util_Binder.h"
+#include "core_jni_helpers.h"
+
+namespace android {
+
+static struct {
+    jclass clazz;
+    jfieldID mNativeObject;
+    jmethodID ctor;
+} gInputTransferTokenClassInfo;
+
+static jlong nativeCreate(JNIEnv* env, jclass clazz) {
+    sp<InputTransferToken> inputTransferToken = sp<InputTransferToken>::make();
+    inputTransferToken->incStrong((void*)nativeCreate);
+    return reinterpret_cast<jlong>(inputTransferToken.get());
+}
+
+static jlong nativeCreateFromBinder(JNIEnv* env, jclass clazz, jobject tokenBinderObj) {
+    if (tokenBinderObj == nullptr) {
+        return 0;
+    }
+    sp<IBinder> token(ibinderForJavaObject(env, tokenBinderObj));
+    if (token == nullptr) {
+        return 0;
+    }
+    sp<InputTransferToken> inputTransferToken = sp<InputTransferToken>::make(token);
+    inputTransferToken->incStrong((void*)nativeCreate);
+    return reinterpret_cast<jlong>(inputTransferToken.get());
+}
+
+static void nativeWriteToParcel(JNIEnv* env, jclass clazz, jlong nativeObj, jobject parcelObj) {
+    InputTransferToken* inputTransferToken = reinterpret_cast<InputTransferToken*>(nativeObj);
+    Parcel* parcel = parcelForJavaObject(env, parcelObj);
+    inputTransferToken->writeToParcel(parcel);
+}
+
+static jlong nativeReadFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj) {
+    sp<InputTransferToken> inputTransferToken = sp<InputTransferToken>::make();
+    Parcel* parcel = parcelForJavaObject(env, parcelObj);
+    inputTransferToken->readFromParcel(parcel);
+    inputTransferToken->incStrong((void*)nativeCreate);
+    return reinterpret_cast<jlong>(inputTransferToken.get());
+}
+
+static jobject nativeGetBinderToken(JNIEnv* env, jclass clazz, jlong nativeObj) {
+    sp<InputTransferToken> inputTransferToken = reinterpret_cast<InputTransferToken*>(nativeObj);
+    return javaObjectForIBinder(env, inputTransferToken->mToken);
+}
+
+InputTransferToken* android_window_InputTransferToken_getNativeInputTransferToken(
+        JNIEnv* env, jobject inputTransferTokenObj) {
+    if (inputTransferTokenObj != nullptr &&
+        env->IsInstanceOf(inputTransferTokenObj, gInputTransferTokenClassInfo.clazz)) {
+        return reinterpret_cast<InputTransferToken*>(
+                env->GetLongField(inputTransferTokenObj,
+                                  gInputTransferTokenClassInfo.mNativeObject));
+    } else {
+        return nullptr;
+    }
+}
+
+jobject android_window_InputTransferToken_getJavaInputTransferToken(
+        JNIEnv* env, const InputTransferToken* inputTransferToken) {
+    if (inputTransferToken == nullptr || env == nullptr) {
+        return nullptr;
+    }
+
+    inputTransferToken->incStrong((void*)nativeCreate);
+    return env->NewObject(gInputTransferTokenClassInfo.clazz, gInputTransferTokenClassInfo.ctor,
+                          reinterpret_cast<jlong>(inputTransferToken));
+}
+
+static void release(InputTransferToken* inputTransferToken) {
+    inputTransferToken->decStrong((void*)nativeCreate);
+}
+
+static jlong nativeGetNativeInputTransferTokenFinalizer(JNIEnv* env, jclass clazz) {
+    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&release));
+}
+
+static bool nativeEquals(JNIEnv* env, jclass clazz, jlong inputTransferTokenObj1,
+                         jlong inputTransferTokenObj2) {
+    sp<InputTransferToken> inputTransferToken1(
+            reinterpret_cast<InputTransferToken*>(inputTransferTokenObj1));
+    sp<InputTransferToken> inputTransferToken2(
+            reinterpret_cast<InputTransferToken*>(inputTransferTokenObj2));
+
+    return inputTransferToken1 == inputTransferToken2;
+}
+
+static const JNINativeMethod sInputTransferTokenMethods[] = {
+        // clang-format off
+    {"nativeCreate", "()J", (void*)nativeCreate},
+    {"nativeCreate", "(Landroid/os/IBinder;)J", (void*)nativeCreateFromBinder},
+    {"nativeWriteToParcel", "(JLandroid/os/Parcel;)V", (void*)nativeWriteToParcel},
+    {"nativeReadFromParcel", "(Landroid/os/Parcel;)J", (void*)nativeReadFromParcel},
+    {"nativeGetBinderToken", "(J)Landroid/os/IBinder;", (void*)nativeGetBinderToken},
+    {"nativeGetNativeInputTransferTokenFinalizer", "()J", (void*)nativeGetNativeInputTransferTokenFinalizer},
+        {"nativeEquals", "(JJ)Z", (void*) nativeEquals},
+        // clang-format on
+};
+
+int register_android_window_InputTransferToken(JNIEnv* env) {
+    int err = RegisterMethodsOrDie(env, "android/window/InputTransferToken",
+                                   sInputTransferTokenMethods, NELEM(sInputTransferTokenMethods));
+    jclass inputTransferTokenClass = FindClassOrDie(env, "android/window/InputTransferToken");
+    gInputTransferTokenClassInfo.clazz = MakeGlobalRefOrDie(env, inputTransferTokenClass);
+    gInputTransferTokenClassInfo.mNativeObject =
+            GetFieldIDOrDie(env, gInputTransferTokenClassInfo.clazz, "mNativeObject", "J");
+    gInputTransferTokenClassInfo.ctor =
+            GetMethodIDOrDie(env, gInputTransferTokenClassInfo.clazz, "<init>", "(J)V");
+    return err;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/core/jni/include/android_runtime/android_window_InputTransferToken.h b/core/jni/include/android_runtime/android_window_InputTransferToken.h
new file mode 100644
index 0000000..75dbe37
--- /dev/null
+++ b/core/jni/include/android_runtime/android_window_InputTransferToken.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_WINDOW_INPUTTRANSFERTOKEN_H
+#define _ANDROID_WINDOW_INPUTTRANSFERTOKEN_H
+
+#include <gui/InputTransferToken.h>
+#include <jni.h>
+
+namespace android {
+
+extern InputTransferToken* android_window_InputTransferToken_getNativeInputTransferToken(
+        JNIEnv* env, jobject inputTransferTokenObj);
+
+extern jobject android_window_InputTransferToken_getJavaInputTransferToken(
+        JNIEnv* env, const InputTransferToken* inputTransferToken);
+
+} // namespace android
+
+#endif // _ANDROID_WINDOW_INPUTTRANSFERTOKEN_H
diff --git a/core/res/Android.bp b/core/res/Android.bp
index e2e419f..79b1376 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -161,6 +161,7 @@
         "camera_platform_flags",
         "android.net.platform.flags-aconfig",
         "com.android.window.flags.window-aconfig",
+        "android.permission.flags-aconfig",
     ],
 }
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1acdc75..12320d7 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3801,12 +3801,12 @@
     <permission android:name="android.permission.MANAGE_DEVICE_POLICY_LOCK"
                 android:protectionLevel="internal|role" />
 
-    <!-- Allows an application to manage policy related to theft detection.
+    <!-- Allows an application to query the device stolen state.
         @FlaggedApi("android.app.admin.flags.device_theft_api_enabled")
         @hide
         @SystemApi
     -->
-    <permission android:name="android.permission.MANAGE_DEVICE_POLICY_THEFT_DETECTION"
+    <permission android:name="android.permission.QUERY_DEVICE_STOLEN_STATE"
                 android:protectionLevel="internal|role" />
 
     <!-- Allows an application to manage policy related to system apps.
@@ -6781,7 +6781,7 @@
          @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt")
     -->
     <permission android:name="android.permission.SET_BIOMETRIC_DIALOG_LOGO"
-                android:protectionLevel="signature" />
+                android:protectionLevel="signature|privileged" />
 
     <!-- Allows an application to control keyguard.  Only allowed for system processes.
         @hide -->
@@ -6916,6 +6916,13 @@
     <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"
+        android:featureFlag="android.permission.flags.sensitive_notification_app_protection"/>
+
     <!-- @SystemApi Allows an application to read install sessions
          @hide This is not a third-party API (intended for system apps). -->
     <permission android:name="android.permission.READ_INSTALL_SESSIONS"
@@ -8143,6 +8150,14 @@
     <permission android:name="android.permission.EMERGENCY_INSTALL_PACKAGES"
                 android:protectionLevel="signature|privileged"/>
 
+    <!-- Allows internal applications to restrict display modes
+       <p>Not for use by third-party applications.
+       <p>Protection level: signature
+       @hide
+    -->
+    <permission android:name="android.permission.RESTRICT_DISPLAY_MODES"
+        android:protectionLevel="signature" />
+
     <!-- Attribution for Geofencing service. -->
     <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
     <!-- Attribution for Country Detector. -->
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/res/res/values/config.xml b/core/res/res/values/config.xml
index f59c099..511c6c9 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3586,14 +3586,6 @@
 
     <!-- The minimum number of visible recent tasks to be presented to the user through the
          SystemUI. Can be -1 if there is no minimum limit. -->
-    <integer name="config_minNumVisibleRecentTasks_grid">-1</integer>
-
-    <!-- The maximum number of visible recent tasks to be presented to the user through the
-         SystemUI. Can be -1 if there is no maximum limit. -->
-    <integer name="config_maxNumVisibleRecentTasks_grid">9</integer>
-
-    <!-- The minimum number of visible recent tasks to be presented to the user through the
-         SystemUI. Can be -1 if there is no minimum limit. -->
     <integer name="config_minNumVisibleRecentTasks_lowRam">-1</integer>
 
     <!-- The maximum number of visible recent tasks to be presented to the user through the
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c603fa7..b11d26e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -392,8 +392,6 @@
   <java-symbol type="string" name="config_hdmiCecSetMenuLanguageActivity" />
   <java-symbol type="integer" name="config_minNumVisibleRecentTasks_lowRam" />
   <java-symbol type="integer" name="config_maxNumVisibleRecentTasks_lowRam" />
-  <java-symbol type="integer" name="config_minNumVisibleRecentTasks_grid" />
-  <java-symbol type="integer" name="config_maxNumVisibleRecentTasks_grid" />
   <java-symbol type="integer" name="config_minNumVisibleRecentTasks" />
   <java-symbol type="integer" name="config_maxNumVisibleRecentTasks" />
   <java-symbol type="integer" name="config_activeTaskDurationHours" />
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..5cc1ee4 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,29 +1249,25 @@
     }
 
     @Test
-    public void testBigPictureStyle_setExtras_pictureIconNull_pictureIconKeyNull() {
+    public void testBigPictureStyle_setExtras_pictureIconNull_noPictureIconKey() {
         Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle();
         bpStyle.bigPicture((Bitmap) null);
 
         Bundle extras = new Bundle();
         bpStyle.addExtras(extras);
 
-        assertThat(extras.containsKey(EXTRA_PICTURE_ICON)).isTrue();
-        final Parcelable pictureIcon = extras.getParcelable(EXTRA_PICTURE_ICON);
-        assertThat(pictureIcon).isNull();
+        assertThat(extras.containsKey(EXTRA_PICTURE_ICON)).isFalse();
     }
 
     @Test
-    public void testBigPictureStyle_setExtras_pictureIconNull_pictureKeyNull() {
+    public void testBigPictureStyle_setExtras_pictureIconNull_noPictureKey() {
         Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle();
         bpStyle.bigPicture((Bitmap) null);
 
         Bundle extras = new Bundle();
         bpStyle.addExtras(extras);
 
-        assertThat(extras.containsKey(EXTRA_PICTURE)).isTrue();
-        final Parcelable picture = extras.getParcelable(EXTRA_PICTURE);
-        assertThat(picture).isNull();
+        assertThat(extras.containsKey(EXTRA_PICTURE)).isFalse();
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
index 2905a5a..e118c98d 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
@@ -31,7 +31,6 @@
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.test.AndroidTestCase;
 import android.util.Log;
-import android.util.Printer;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -47,15 +46,12 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.Vector;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Phaser;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -407,138 +403,4 @@
         }
         assertFalse(allowed);
     }
-
-    /** Dumpsys information about a single database. */
-
-    /**
-     * Collect and parse dumpsys output.  This is not a full parser.  It is only enough to support
-     * the unit tests.
-     */
-    private static class Dumpsys {
-        // Regular expressions for parsing the output.  Reportedly, regular expressions are
-        // expensive, so these are created only if a dumpsys object is created.
-        private static final Object sLock = new Object();
-        static Pattern mPool;
-        static Pattern mConnection;
-        static Pattern mEntry;
-        static Pattern mSingleWord;
-        static Pattern mNone;
-
-        // The raw strings read from dumpsys.  Once loaded, this list never changes.
-        final ArrayList<String> mRaw = new ArrayList<>();
-
-        // Parsed dumpsys.  This contains only the bits that are being tested.
-        static class Connection {
-            ArrayList<String> mRecent = new ArrayList<>();
-            ArrayList<String> mLong = new ArrayList<>();
-        }
-        static class Database {
-            String mPath;
-            ArrayList<Connection> mConnection = new ArrayList<>();
-        }
-        ArrayList<Database> mDatabase;
-        ArrayList<String> mConcurrent;
-
-        Dumpsys() {
-            SQLiteDebug.dump(
-                new Printer() { public void println(String x) { mRaw.add(x); } },
-                new String[0]);
-            parse();
-        }
-
-        /** Parse the raw text. Return true if no errors were detected. */
-        boolean parse() {
-            initialize();
-
-            // Reset the parsed information.  This method may be called repeatedly.
-            mDatabase = new ArrayList<>();
-            mConcurrent = new ArrayList<>();
-
-            Database current = null;
-            Connection connection = null;
-            Matcher matcher;
-            for (int i = 0; i < mRaw.size(); i++) {
-                final String line = mRaw.get(i);
-                matcher = mPool.matcher(line);
-                if (matcher.lookingAt()) {
-                    current = new Database();
-                    mDatabase.add(current);
-                    current.mPath = matcher.group(1);
-                    continue;
-                }
-                matcher = mConnection.matcher(line);
-                if (matcher.lookingAt()) {
-                    connection = new Connection();
-                    current.mConnection.add(connection);
-                    continue;
-                }
-
-                if (line.contains("Most recently executed operations")) {
-                    i += readTable(connection.mRecent, i, mEntry);
-                    continue;
-                }
-
-                if (line.contains("Operations exceeding 2000ms")) {
-                    i += readTable(connection.mLong, i, mEntry);
-                    continue;
-                }
-                if (line.contains("Concurrently opened database files")) {
-                    i += readTable(mConcurrent, i, mSingleWord);
-                    continue;
-                }
-            }
-            return true;
-        }
-
-        /**
-         * Read a series of lines following a header.  Return the number of lines read.  The input
-         * line number is the number of the header.
-         */
-        private int readTable(List<String> s, int header, Pattern p) {
-            // Special case: if the first line is "<none>" then there are no more lines to the
-            // table.
-            if (lookingAt(header+1, mNone)) return 1;
-
-            int i;
-            for (i = header + 1; i < mRaw.size() && lookingAt(i, p); i++) {
-                s.add(mRaw.get(i).trim());
-            }
-            return i - header;
-        }
-
-        /** Return true if the n'th raw line matches the pattern. */
-        boolean lookingAt(int n, Pattern p) {
-            return p.matcher(mRaw.get(n)).lookingAt();
-        }
-
-        /** Compile the regular expressions the first time. */
-        private static void initialize() {
-            synchronized (sLock) {
-                if (mPool != null) return;
-                mPool = Pattern.compile("Connection pool for (\\S+):");
-                mConnection = Pattern.compile("\\s+Connection #(\\d+):");
-                mEntry = Pattern.compile("\\s+(\\d+): ");
-                mSingleWord = Pattern.compile("  (\\S+)$");
-                mNone = Pattern.compile("\\s+<none>$");
-            }
-        }
-    }
-
-    @Test
-    public void testDumpsys() throws Exception {
-        Dumpsys dumpsys = new Dumpsys();
-
-        assertEquals(1, dumpsys.mDatabase.size());
-        // Note: cannot test mConcurrent because that attribute is not hermitic with respect to
-        // the tests.
-
-        Dumpsys.Database db = dumpsys.mDatabase.get(0);
-
-        // Work with normalized paths.
-        String wantPath = mDatabaseFile.toPath().toRealPath().toString();
-        String realPath = new File(db.mPath).toPath().toRealPath().toString();
-        assertEquals(wantPath, realPath);
-
-        assertEquals(1, db.mConnection.size());
-    }
 }
diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
index 48ba526..fc1fbb5 100644
--- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -120,6 +120,10 @@
             final var statsToken = ImeTracker.Token.empty();
             mImeConsumer.onWindowFocusGained(true);
             mController.show(WindowInsets.Type.ime(), true /* fromIme */, statsToken);
+            // Called once through the show flow.
+            verify(mController).applyAnimation(
+                    eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */,
+                    eq(statsToken));
 
             // set control and verify visibility is applied.
             InsetsSourceControl control = new InsetsSourceControl(ID_IME,
@@ -127,11 +131,11 @@
             mController.onControlsChanged(new InsetsSourceControl[] { control });
             // IME show animation should be triggered when control becomes available.
             verify(mController).applyAnimation(
-                    eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */,
-                    eq(statsToken));
+                    eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(false) /* fromIme */,
+                    and(not(eq(statsToken)), notNull()));
             verify(mController, never()).applyAnimation(
-                    eq(WindowInsets.Type.ime()), eq(false) /* show */, eq(true) /* fromIme */,
-                    eq(statsToken));
+                    eq(WindowInsets.Type.ime()), eq(false) /* show */, eq(false) /* fromIme */,
+                    and(not(eq(statsToken)), notNull()));
         });
     }
 
@@ -159,6 +163,10 @@
             final var statsToken = ImeTracker.Token.empty();
             if (imeVisible) {
                 mController.show(WindowInsets.Type.ime(), true /* fromIme */, statsToken);
+                // Called once through the show flow.
+                verify(mController).applyAnimation(eq(WindowInsets.Type.ime()),
+                        eq(true) /* show */, eq(true) /* fromIme */,
+                        eq(false) /* skipAnim */, eq(statsToken));
             }
 
             // set control and verify visibility is applied.
@@ -184,13 +192,17 @@
             if (!hasViewFocus) {
                 final var statsTokenNext = ImeTracker.Token.empty();
                 mController.show(WindowInsets.Type.ime(), true /* fromIme */, statsTokenNext);
+                // Called once through the show flow.
+                verify(mController).applyAnimation(eq(WindowInsets.Type.ime()),
+                        eq(true) /* show */, eq(true) /* fromIme */,
+                        eq(false) /* skipAnim */, eq(statsTokenNext));
                 mController.onControlsChanged(new InsetsSourceControl[]{ control });
                 // Verify IME show animation should be triggered when control becomes available and
                 // the animation will be skipped by getAndClearSkipAnimationOnce invoked.
                 verify(control).getAndClearSkipAnimationOnce();
                 verify(mController).applyAnimation(eq(WindowInsets.Type.ime()),
-                        eq(true) /* show */, eq(true) /* fromIme */,
-                        eq(false) /* skipAnim */, eq(statsTokenNext));
+                        eq(true) /* show */, eq(false) /* fromIme */,
+                        eq(true) /* skipAnim */, and(not(eq(statsToken)), notNull()));
             }
         });
     }
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 2544fcb..652011b 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -679,20 +679,20 @@
         }
 
         sInstrumentation.runOnMainSync(() -> {
-            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW);
+            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW, 0, null);
             assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW);
-            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
+            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL, 0, null);
             assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
-            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT);
+            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT, 0, null);
             assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
                     FRAME_RATE_CATEGORY_HIGH_HINT);
-            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH);
+            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH, 0, null);
             assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
-            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT);
+            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT, 0, null);
             assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
-            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
+            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL, 0, null);
             assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
-            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW);
+            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW, 0, null);
             assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
         });
     }
diff --git a/core/tests/coretests/src/android/view/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/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
index 43e6227..dbabcea 100644
--- a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
@@ -641,6 +641,7 @@
     public void shouldForwardToParent_telephony_privateProfile() throws Exception {
         mSetFlagsRule.enableFlags(
                 android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES,
                 android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_INTENT_REDIRECTION);
 
         sComponentName = FORWARD_TO_PARENT_COMPONENT_NAME;
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/core.protolog.pb b/data/etc/core.protolog.pb
index 0415e44..e1670be 100644
--- a/data/etc/core.protolog.pb
+++ b/data/etc/core.protolog.pb
Binary files differ
diff --git a/data/etc/enhanced-confirmation.xml b/data/etc/enhanced-confirmation.xml
index 3b1867c..973bcb5 100644
--- a/data/etc/enhanced-confirmation.xml
+++ b/data/etc/enhanced-confirmation.xml
@@ -24,33 +24,49 @@
     <enhanced-confirmation-trusted-package
          package="com.example.app"
          sha256-cert-digest="E9:7A:BC:2C:D1:CA:8D:58:6A:57:0B:8C:F8:60:AA:D2:8D:13:30:2A:FB:C9:00:2C:5D:53:B2:6C:09:A4:85:A0"/>
-
     ...
 
     <enhanced-confirmation-trusted-installer
          package="com.example.installer"
          sha256-cert-digest="E9:7A:BC:2C:D1:CA:8D:58:6A:57:0B:8C:F8:60:AA:D2:8D:13:30:2A:FB:C9:00:2C:5D:53:B2:6C:09:A4:85:A0"/>
-
     ...
 
-The "enhanced-confirmation-trusted-package" entry shown above indicates that "com.example.app"
-should be considered a "trusted package". A "trusted package" will be exempt from ECM restrictions.
+The <enhanced-confirmation-trusted-package> entry shown in the above example indicates that
+"com.example.app" should be considered a "trusted package". A "trusted package" will be exempt from
+ECM restrictions.
 
-The "enhanced-confirmation-trusted-installer" entry shown above indicates that
-"com.example.installer" should be considered a "trusted installer". A "trusted installer", and all
-packages that it installs, will be exempt from ECM restrictions. (There are some exceptions to this.
-For example, a trusted installer, at the time of installing an app, can opt the app back in to ECM
-restrictions by setting the app's package source to PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE
-or PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE.)
+The <enhanced-confirmation-trusted-installer> entry shown in the above example indicates that
+"com.example.app" should be considered a "trusted installer". Apps installed by "trusted installers"
+will be exempt from ECM restrictions, with conditions explained in the next few paragraphs.
 
-In either case:
+If zero <enhanced-confirmation-trusted-installer> entries are declared, then *all* packages will be
+exempt from ECM restrictions, except apps meeting *all* of the following criteria:
+
+    A. The app is not pre-installed, and
+    B. The app has no matching <enhanced-confirmation-trusted-package> entries declared, and
+    C. The app is marked by its installer as coming from an untrustworthy package source.
+
+(For example, an installer may set an app's package source to
+PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE or PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE,
+which are considered untrustworthy.)
+
+If one or more <enhanced-confirmation-trusted-installer> entries are declared, then packages must,
+in order to be exempt from ECM, meet at least one of the following criteria:
+
+    A. Be installed by an installer with a matching <enhanced-confirmation-trusted-installer> entry
+       declared, and be marked as coming from an "trustworthy" package source by the installer, or
+    B. Be installed via a pre-installed installer, and be marked as coming from a "trustworthy"
+       package source by the installer, or
+    C. Have a matching <enhanced-confirmation-trusted-package> entry declared.
+
+For either type of XML element:
 
 - The "package" XML attribute refers to the app's package name.
 - The "sha256-cert-digest" XML attribute refers to the SHA-256 hash of an app signing certificate.
 
 For any entry to successfully apply to a package, both XML attributes must be present, and must
 match the package. That is, the package name must match the "package" attribute, and the app must be
-signed by the signing certificate identified by the "sha256-cert-digest" attribute..
+signed by the signing certificate identified by the "sha256-cert-digest" attribute.
 -->
 
 <config></config>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 0f12438..749f0e1 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -553,6 +553,8 @@
         <permission name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT"/>
         <!-- Permission required for CTS test - CtsWearableSensingServiceTestCases -->
         <permission name="android.permission.MANAGE_WEARABLE_SENSING_SERVICE"/>
+        <!-- Permission required for CTS test - OnDeviceIntelligenceManagerTest -->
+        <permission name="android.permission.USE_ON_DEVICE_INTELLIGENCE" />
         <!-- Permission required for CTS test - CtsTelephonyProviderTestCases -->
         <permission name="android.permission.WRITE_APN_SETTINGS"/>
         <!-- Permission required for GTS test - GtsStatsdHostTestCases -->
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 0231d3a..d410d5f 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",
@@ -3433,6 +3421,24 @@
       "group": "WM_DEBUG_WALLPAPER",
       "at": "com\/android\/server\/wm\/WallpaperController.java"
     },
+    "-6856158722649737204": {
+      "message": "Waiting for offset complete...",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WALLPAPER",
+      "at": "com\/android\/server\/wm\/WallpaperController.java"
+    },
+    "-5966696477376431672": {
+      "message": "Offset complete!",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WALLPAPER",
+      "at": "com\/android\/server\/wm\/WallpaperController.java"
+    },
+    "4198834090919802045": {
+      "message": "Timeout waiting for wallpaper to offset: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WALLPAPER",
+      "at": "com\/android\/server\/wm\/WallpaperController.java"
+    },
     "-3477087868568520027": {
       "message": "No longer animating wallpaper targets!",
       "level": "VERBOSE",
@@ -3463,8 +3469,32 @@
       "group": "WM_DEBUG_WALLPAPER",
       "at": "com\/android\/server\/wm\/WallpaperController.java"
     },
-    "-2504764636812266719": {
-      "message": "New wallpaper: target=%s prev=%s",
+    "7408402065665963407": {
+      "message": "Wallpaper at display %d - visibility: %b, keyguardLocked: %b",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WALLPAPER",
+      "at": "com\/android\/server\/wm\/WallpaperController.java"
+    },
+    "-8598497865499265448": {
+      "message": "Wallpaper target=%s prev=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_WALLPAPER",
+      "at": "com\/android\/server\/wm\/WallpaperController.java"
+    },
+    "-5402010429724738603": {
+      "message": "Wallpaper should be visible but has not been drawn yet. mWallpaperDrawState=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WALLPAPER",
+      "at": "com\/android\/server\/wm\/WallpaperController.java"
+    },
+    "4151327328872447804": {
+      "message": "New home screen wallpaper: %s, prev: %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_WALLPAPER",
+      "at": "com\/android\/server\/wm\/WallpaperController.java"
+    },
+    "6943105284590482059": {
+      "message": "New lock\/shared screen wallpaper: %s, prev: %s",
       "level": "DEBUG",
       "group": "WM_DEBUG_WALLPAPER",
       "at": "com\/android\/server\/wm\/WallpaperController.java"
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
index fa35b63..98935e9 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
@@ -19,13 +19,14 @@
 import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
 
 import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_UNKNOWN;
-import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_USE_BASE_STATE;
 import static androidx.window.common.CommonFoldingFeature.parseListFromString;
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.hardware.devicestate.DeviceState;
 import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
+import android.hardware.devicestate.DeviceStateUtil;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseIntArray;
@@ -54,29 +55,27 @@
     private static final boolean DEBUG = false;
 
     /**
-     * Emulated device state {@link DeviceStateManager.DeviceStateCallback#onStateChanged(int)} to
+     * Emulated device state
+     * {@link DeviceStateManager.DeviceStateCallback#onDeviceStateChanged(DeviceState)} to
      * {@link CommonFoldingFeature.State} map.
      */
     private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray();
 
     /**
-     * Emulated device state received via
-     * {@link DeviceStateManager.DeviceStateCallback#onStateChanged(int)}.
-     * "Emulated" states differ from "base" state in the sense that they may not correspond 1:1 with
-     * physical device states. They represent the state of the device when various software
-     * features and APIs are applied. The emulated states generally consist of all "base" states,
-     * but may have additional states such as "concurrent" or "rear display". Concurrent mode for
-     * example is activated via public API and can be active in both the "open" and "half folded"
-     * device states.
+     * Device state received via
+     * {@link DeviceStateManager.DeviceStateCallback#onDeviceStateChanged(DeviceState)}.
+     * The identifier returned through {@link DeviceState#getIdentifier()} may not correspond 1:1
+     * with the physical state of the device. This could correspond to the system state of the
+     * device when various software features or overrides are applied. The emulated states generally
+     * consist of all "base" states, but may have additional states such as "concurrent" or
+     * "rear display". Concurrent mode for example is activated via public API and can be active in
+     * both the "open" and "half folded" device states.
      */
-    private int mCurrentDeviceState = INVALID_DEVICE_STATE_IDENTIFIER;
+    private DeviceState mCurrentDeviceState = new DeviceState(
+            new DeviceState.Configuration.Builder(INVALID_DEVICE_STATE_IDENTIFIER,
+                    "INVALID").build());
 
-    /**
-     * Base device state received via
-     * {@link DeviceStateManager.DeviceStateCallback#onBaseStateChanged(int)}.
-     * "Base" in this context means the "physical" state of the device.
-     */
-    private int mCurrentBaseDeviceState = INVALID_DEVICE_STATE_IDENTIFIER;
+    private List<DeviceState> mSupportedStates;
 
     @NonNull
     private final RawFoldingFeatureProducer mRawFoldSupplier;
@@ -85,22 +84,11 @@
 
     private final DeviceStateCallback mDeviceStateCallback = new DeviceStateCallback() {
         @Override
-        public void onStateChanged(int state) {
+        public void onDeviceStateChanged(@NonNull DeviceState state) {
             mCurrentDeviceState = state;
             mRawFoldSupplier.getData(DeviceStateManagerFoldingFeatureProducer
                     .this::notifyFoldingFeatureChange);
         }
-
-        @Override
-        public void onBaseStateChanged(int state) {
-            mCurrentBaseDeviceState = state;
-
-            if (mDeviceStateToPostureMap.get(mCurrentDeviceState)
-                    == COMMON_STATE_USE_BASE_STATE) {
-                mRawFoldSupplier.getData(DeviceStateManagerFoldingFeatureProducer
-                        .this::notifyFoldingFeatureChange);
-            }
-        }
     };
 
     public DeviceStateManagerFoldingFeatureProducer(@NonNull Context context,
@@ -109,6 +97,7 @@
         mRawFoldSupplier = rawFoldSupplier;
         String[] deviceStatePosturePairs = context.getResources()
                 .getStringArray(R.array.config_device_state_postures);
+        mSupportedStates = deviceStateManager.getSupportedDeviceStates();
         boolean isHalfOpenedSupported = false;
         for (String deviceStatePosturePair : deviceStatePosturePairs) {
             String[] deviceStatePostureMapping = deviceStatePosturePair.split(":");
@@ -168,7 +157,7 @@
      */
     private boolean isCurrentStateValid() {
         // If the device state is not found in the map, indexOfKey returns a negative number.
-        return mDeviceStateToPostureMap.indexOfKey(mCurrentDeviceState) >= 0;
+        return mDeviceStateToPostureMap.indexOfKey(mCurrentDeviceState.getIdentifier()) >= 0;
     }
 
     @Override
@@ -177,7 +166,9 @@
         if (hasListeners()) {
             mRawFoldSupplier.addDataChangedCallback(this::notifyFoldingFeatureChange);
         } else {
-            mCurrentDeviceState = INVALID_DEVICE_STATE_IDENTIFIER;
+            mCurrentDeviceState = new DeviceState(
+                    new DeviceState.Configuration.Builder(INVALID_DEVICE_STATE_IDENTIFIER,
+                            "INVALID").build());
             mRawFoldSupplier.removeDataChangedCallback(this::notifyFoldingFeatureChange);
         }
     }
@@ -251,10 +242,13 @@
     @CommonFoldingFeature.State
     private int currentHingeState() {
         @CommonFoldingFeature.State
-        int posture = mDeviceStateToPostureMap.get(mCurrentDeviceState, COMMON_STATE_UNKNOWN);
+        int posture = mDeviceStateToPostureMap.get(mCurrentDeviceState.getIdentifier(),
+                COMMON_STATE_UNKNOWN);
 
         if (posture == CommonFoldingFeature.COMMON_STATE_USE_BASE_STATE) {
-            posture = mDeviceStateToPostureMap.get(mCurrentBaseDeviceState, COMMON_STATE_UNKNOWN);
+            posture = mDeviceStateToPostureMap.get(
+                    DeviceStateUtil.calculateBaseStateIdentifier(mCurrentDeviceState,
+                            mSupportedStates), COMMON_STATE_UNKNOWN);
         }
 
         return posture;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index d31bf2a..a3d2d7f 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -20,6 +20,7 @@
 
 import android.app.Activity;
 import android.content.Context;
+import android.hardware.devicestate.DeviceState;
 import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.devicestate.DeviceStateRequest;
 import android.hardware.display.DisplayManager;
@@ -40,6 +41,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
@@ -101,7 +103,9 @@
         mDisplayManager = context.getSystemService(DisplayManager.class);
         mExecutor = context.getMainExecutor();
 
-        mCurrentSupportedDeviceStates = mDeviceStateManager.getSupportedStates();
+        // TODO(b/329436166): Update the usage of device state manager API's
+        mCurrentSupportedDeviceStates = getSupportedStateIdentifiers(
+                mDeviceStateManager.getSupportedDeviceStates());
         mFoldedDeviceStates = context.getResources().getIntArray(
                 R.array.config_foldedDeviceStates);
 
@@ -446,9 +450,10 @@
     }
 
     @Override
-    public void onSupportedStatesChanged(int[] supportedStates) {
+    public void onSupportedStatesChanged(@NonNull List<DeviceState> supportedStates) {
         synchronized (mLock) {
-            mCurrentSupportedDeviceStates = supportedStates;
+            // TODO(b/329436166): Update the usage of device state manager API's
+            mCurrentSupportedDeviceStates = getSupportedStateIdentifiers(supportedStates);
             updateRearDisplayStatusListeners(getCurrentRearDisplayModeStatus());
             updateRearDisplayPresentationStatusListeners(
                     getCurrentRearDisplayPresentationModeStatus());
@@ -456,9 +461,10 @@
     }
 
     @Override
-    public void onStateChanged(int state) {
+    public void onDeviceStateChanged(@NonNull DeviceState state) {
         synchronized (mLock) {
-            mCurrentDeviceState = state;
+            // TODO(b/329436166): Update the usage of device state manager API's
+            mCurrentDeviceState = state.getIdentifier();
             updateRearDisplayStatusListeners(getCurrentRearDisplayModeStatus());
             updateRearDisplayPresentationStatusListeners(
                     getCurrentRearDisplayPresentationModeStatus());
@@ -482,6 +488,15 @@
         return WindowAreaComponent.STATUS_AVAILABLE;
     }
 
+    // TODO(b/329436166): Remove and update the usage of device state manager API's
+    private int[] getSupportedStateIdentifiers(@NonNull List<DeviceState> states) {
+        int[] identifiers = new int[states.size()];
+        for (int i = 0; i < states.size(); i++) {
+            identifiers[i] = states.get(i).getIdentifier();
+        }
+        return identifiers;
+    }
+
     /**
      * Helper method to determine if a rear display session is currently active by checking
      * if the current device state is that which corresponds to {@code mRearDisplayState}.
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
index 8989fc5..f3d70f7 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
@@ -18,27 +18,32 @@
 
 import android.content.Context
 import android.content.Intent
+import android.content.pm.ShortcutInfo
+import android.content.res.Resources
 import android.graphics.Color
 import android.graphics.drawable.Icon
 import android.os.UserHandle
 import android.view.IWindowManager
 import android.view.WindowManager
 import android.view.WindowManagerGlobal
-import androidx.test.annotation.UiThreadTest
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.internal.protolog.common.ProtoLog
 import com.android.launcher3.icons.BubbleIconFactory
 import com.android.wm.shell.R
+import com.android.wm.shell.animation.PhysicsAnimatorTestUtils
 import com.android.wm.shell.bubbles.Bubbles.SysuiProxy
+import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix
 import com.android.wm.shell.common.FloatingContentCoordinator
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.taskview.TaskView
 import com.android.wm.shell.taskview.TaskViewTaskController
 import com.google.common.truth.Truth.assertThat
 import com.google.common.util.concurrent.MoreExecutors.directExecutor
+import org.junit.After
 import java.util.concurrent.Semaphore
 import java.util.concurrent.TimeUnit
 import java.util.function.Consumer
@@ -64,6 +69,7 @@
 
     @Before
     fun setUp() {
+        PhysicsAnimatorTestUtils.prepareForTest()
         // Disable protolog tool when running the tests from studio
         ProtoLog.REQUIRE_PROTOLOGTOOL = false
         windowManager = WindowManagerGlobal.getWindowManagerService()!!
@@ -104,34 +110,158 @@
                 { sysuiProxy },
                 shellExecutor
             )
+
+        context
+            .getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
+            .edit()
+            .putBoolean(StackEducationView.PREF_STACK_EDUCATION, true)
+            .apply()
     }
 
-    @UiThreadTest
+    @After
+    fun tearDown() {
+        PhysicsAnimatorTestUtils.tearDown()
+    }
+
     @Test
     fun addBubble() {
         val bubble = createAndInflateBubble()
-        bubbleStackView.addBubble(bubble)
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            bubbleStackView.addBubble(bubble)
+        }
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync()
         assertThat(bubbleStackView.bubbleCount).isEqualTo(1)
     }
 
-    @UiThreadTest
     @Test
     fun tapBubbleToExpand() {
         val bubble = createAndInflateBubble()
-        bubbleStackView.addBubble(bubble)
-        assertThat(bubbleStackView.bubbleCount).isEqualTo(1)
 
-        bubble.iconView!!.performClick()
-        // we're checking the expanded state in BubbleData because that's the source of truth. This
-        // will eventually propagate an update back to the stack view, but setting the entire
-        // pipeline is outside the scope of a unit test.
-        assertThat(bubbleData.isExpanded).isTrue()
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            bubbleStackView.addBubble(bubble)
+        }
+
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+        assertThat(bubbleStackView.bubbleCount).isEqualTo(1)
+        var lastUpdate: BubbleData.Update? = null
+        val semaphore = Semaphore(0)
+        val listener =
+            BubbleData.Listener { update ->
+                lastUpdate = update
+                semaphore.release()
+            }
+        bubbleData.setListener(listener)
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            bubble.iconView!!.performClick()
+            // we're checking the expanded state in BubbleData because that's the source of truth.
+            // This will eventually propagate an update back to the stack view, but setting the
+            // entire pipeline is outside the scope of a unit test.
+            assertThat(bubbleData.isExpanded).isTrue()
+        }
+
+        assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+        assertThat(lastUpdate).isNotNull()
+        assertThat(lastUpdate!!.expandedChanged).isTrue()
+        assertThat(lastUpdate!!.expanded).isTrue()
+    }
+
+    @Test
+    fun tapDifferentBubble_shouldReorder() {
+        val bubble1 = createAndInflateChatBubble(key = "bubble1")
+        val bubble2 = createAndInflateChatBubble(key = "bubble2")
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            bubbleStackView.addBubble(bubble1)
+            bubbleStackView.addBubble(bubble2)
+        }
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+
+        assertThat(bubbleStackView.bubbleCount).isEqualTo(2)
+        assertThat(bubbleData.bubbles).hasSize(2)
+        assertThat(bubbleData.selectedBubble).isEqualTo(bubble2)
+        assertThat(bubble2.iconView).isNotNull()
+
+        var lastUpdate: BubbleData.Update? = null
+        val semaphore = Semaphore(0)
+        val listener =
+            BubbleData.Listener { update ->
+                lastUpdate = update
+                semaphore.release()
+            }
+        bubbleData.setListener(listener)
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            bubble2.iconView!!.performClick()
+            assertThat(bubbleData.isExpanded).isTrue()
+
+            bubbleStackView.setSelectedBubble(bubble2)
+            bubbleStackView.isExpanded = true
+        }
+
+        assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+        assertThat(lastUpdate!!.expanded).isTrue()
+        assertThat(lastUpdate!!.bubbles.map { it.key })
+            .containsExactly("bubble2", "bubble1")
+            .inOrder()
+
+        // wait for idle to allow the animation to start
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+        // wait for the expansion animation to complete before interacting with the bubbles
+        PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(
+                AnimatableScaleMatrix.SCALE_X, AnimatableScaleMatrix.SCALE_Y)
+
+        // tap on bubble1 to select it
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            bubble1.iconView!!.performClick()
+        }
+        assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+        assertThat(bubbleData.selectedBubble).isEqualTo(bubble1)
+
+        // tap on bubble1 again to collapse the stack
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            // we have to set the selected bubble in the stack view manually because we don't have a
+            // listener wired up.
+            bubbleStackView.setSelectedBubble(bubble1)
+            bubble1.iconView!!.performClick()
+        }
+
+        assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+        assertThat(bubbleData.selectedBubble).isEqualTo(bubble1)
+        assertThat(bubbleData.isExpanded).isFalse()
+        assertThat(lastUpdate!!.orderChanged).isTrue()
+        assertThat(lastUpdate!!.bubbles.map { it.key })
+            .containsExactly("bubble1", "bubble2")
+            .inOrder()
+    }
+
+    private fun createAndInflateChatBubble(key: String): Bubble {
+        val icon = Icon.createWithResource(context.resources, R.drawable.bubble_ic_overflow_button)
+        val shortcutInfo = ShortcutInfo.Builder(context, "fakeId").setIcon(icon).build()
+        val bubble =
+            Bubble(
+                key,
+                shortcutInfo,
+                /* desiredHeight= */ 6,
+                Resources.ID_NULL,
+                "title",
+                /* taskId= */ 0,
+                "locus",
+                /* isDismissable= */ true,
+                directExecutor()
+            ) {}
+        inflateBubble(bubble)
+        return bubble
     }
 
     private fun createAndInflateBubble(): Bubble {
         val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
         val icon = Icon.createWithResource(context.resources, R.drawable.bubble_ic_overflow_button)
         val bubble = Bubble.createAppBubble(intent, UserHandle(1), icon, directExecutor())
+        inflateBubble(bubble)
+        return bubble
+    }
+
+    private fun inflateBubble(bubble: Bubble) {
         bubble.setInflateSynchronously(true)
         bubbleData.notificationEntryUpdated(bubble, true, false)
 
@@ -152,7 +282,6 @@
 
         assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
         assertThat(bubble.isInflated).isTrue()
-        return bubble
     }
 
     private class FakeBubbleStackViewManager : BubbleStackViewManager {
@@ -176,7 +305,7 @@
             r.run()
         }
 
-        override fun removeCallbacks(r: Runnable) {}
+        override fun removeCallbacks(r: Runnable?) {}
 
         override fun hasCallback(r: Runnable): Boolean = false
     }
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 7dd3961..00fb298 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -272,6 +272,10 @@
     <dimen name="bubble_bar_expanded_view_corner_radius">16dp</dimen>
     <!-- Corner radius for expanded view while it is being dragged -->
     <dimen name="bubble_bar_expanded_view_corner_radius_dragged">28dp</dimen>
+    <!-- Width of the box around bottom center of the screen where drag only leads to dismiss -->
+    <dimen name="bubble_bar_dismiss_zone_width">192dp</dimen>
+    <!-- Height of the box around bottom center of the screen where drag only leads to dismiss -->
+    <dimen name="bubble_bar_dismiss_zone_height">242dp</dimen>
 
     <!-- Bottom and end margin for compat buttons. -->
     <dimen name="compat_button_margin">24dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
index ee8c414..b7f0890 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
@@ -505,7 +505,6 @@
             // Check for a spring configuration. If one is present, we're either springing, or
             // flinging-then-springing.
             if (springConfig != null) {
-
                 // If there is no corresponding fling config, we're only springing.
                 if (flingConfig == null) {
                     // Apply the configuration and start the animation.
@@ -679,7 +678,6 @@
             value: Float,
             velocity: Float
         ) {
-
             // If this property animation isn't relevant to this listener, ignore it.
             if (!properties.contains(property)) {
                 return
@@ -702,7 +700,6 @@
             finalVelocity: Float,
             isFling: Boolean
         ): Boolean {
-
             // If this property animation isn't relevant to this listener, ignore it.
             if (!properties.contains(property)) {
                 return false
@@ -971,17 +968,18 @@
     companion object {
 
         /**
-         * Constructor to use to for new physics animator instances in [getInstance]. This is
-         * typically the default constructor, but [PhysicsAnimatorTestUtils] can change it so that
-         * all code using the physics animator is given testable instances instead.
+         * Callback to notify that a new animator was created. Used in [PhysicsAnimatorTestUtils]
+         * to be able to keep track of animators and wait for them to finish.
          */
-        internal var instanceConstructor: (Any) -> PhysicsAnimator<*> = ::PhysicsAnimator
+        internal var onAnimatorCreated: (PhysicsAnimator<*>, Any) -> Unit = { _, _ -> }
 
         @JvmStatic
         @Suppress("UNCHECKED_CAST")
         fun <T : Any> getInstance(target: T): PhysicsAnimator<T> {
             if (!animators.containsKey(target)) {
-                animators[target] = instanceConstructor(target)
+                val animator = PhysicsAnimator(target)
+                onAnimatorCreated(animator, target)
+                animators[target] = animator
             }
 
             return animators[target] as PhysicsAnimator<T>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimatorTestUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimatorTestUtils.kt
index 86eb8da..7defc26 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimatorTestUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimatorTestUtils.kt
@@ -62,12 +62,9 @@
      */
     @JvmStatic
     fun prepareForTest() {
-        val defaultConstructor = PhysicsAnimator.instanceConstructor
-        PhysicsAnimator.instanceConstructor = fun(target: Any): PhysicsAnimator<*> {
-            val animator = defaultConstructor(target)
+        PhysicsAnimator.onAnimatorCreated = { animator, target ->
             allAnimatedObjects.add(target)
             animatorTestHelpers[animator] = AnimatorTestHelper(animator)
-            return animator
         }
 
         timeoutMs = 2000
@@ -158,12 +155,12 @@
     @Throws(InterruptedException::class)
     @Suppress("UNCHECKED_CAST")
     fun <T : Any> blockUntilAnimationsEnd(
-        properties: FloatPropertyCompat<in T>
+        vararg properties: FloatPropertyCompat<in T>
     ) {
         for (target in allAnimatedObjects) {
             try {
                 blockUntilAnimationsEnd(
-                        PhysicsAnimator.getInstance(target) as PhysicsAnimator<T>, properties)
+                        PhysicsAnimator.getInstance(target) as PhysicsAnimator<T>, *properties)
             } catch (e: ClassCastException) {
                 // Keep checking the other objects for ones whose types match the provided
                 // properties.
@@ -267,10 +264,8 @@
 
         // Loop through the updates from the testable animator.
         for (update in framesForProperty) {
-
             // Check whether this frame satisfies the current matcher.
             if (curMatcher(update)) {
-
                 // If that was the last unsatisfied matcher, we're good here. 'Verify' all remaining
                 // frames and return without failing.
                 if (matchers.size == 0) {
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/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
index 056598b..b5b8a81 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
@@ -17,8 +17,10 @@
 package com.android.wm.shell.bubbles.bar
 
 import android.annotation.SuppressLint
+import android.graphics.RectF
 import android.view.MotionEvent
 import android.view.View
+import com.android.wm.shell.R
 import com.android.wm.shell.bubbles.BubblePositioner
 import com.android.wm.shell.common.bubbles.BubbleBarLocation
 import com.android.wm.shell.common.bubbles.DismissView
@@ -43,6 +45,8 @@
     private val magnetizedExpandedView: MagnetizedObject<BubbleBarExpandedView> =
         MagnetizedObject.magnetizeView(expandedView)
     private val magnetizedDismissTarget: MagnetizedObject.MagneticTarget
+    private val dismissZoneHeight: Int
+    private val dismissZoneWidth: Int
 
     init {
         magnetizedExpandedView.magnetListener = MagnetListener()
@@ -74,6 +78,11 @@
             }
             return@setOnTouchListener dragMotionEventHandler.onTouch(view, event) || magnetConsumed
         }
+
+        dismissZoneHeight =
+            dismissView.resources.getDimensionPixelSize(R.dimen.bubble_bar_dismiss_zone_height)
+        dismissZoneWidth =
+            dismissView.resources.getDimensionPixelSize(R.dimen.bubble_bar_dismiss_zone_width)
     }
 
     /** Listener to receive callback about dragging events */
@@ -97,12 +106,23 @@
         private var isMoving = false
         private var screenCenterX: Int = -1
         private var isOnLeft = false
+        private val dismissZone = RectF()
 
         override fun onDown(v: View, ev: MotionEvent): Boolean {
             // While animating, don't allow new touch events
             if (expandedView.isAnimating) return false
-            screenCenterX = bubblePositioner.screenRect.centerX()
             isOnLeft = bubblePositioner.isBubbleBarOnLeft
+
+            val screenRect = bubblePositioner.screenRect
+            screenCenterX = screenRect.centerX()
+            val screenBottom = screenRect.bottom
+
+            dismissZone.set(
+                screenCenterX - dismissZoneWidth / 2f,
+                (screenBottom - dismissZoneHeight).toFloat(),
+                screenCenterX + dismissZoneHeight / 2f,
+                screenBottom.toFloat()
+            )
             return true
         }
 
@@ -122,6 +142,11 @@
             expandedView.translationY = expandedViewInitialTranslationY + dy
             dismissView.show()
 
+            // Check if we are in the zone around dismiss view where drag can only lead to dismiss
+            if (dismissZone.contains(ev.rawX, ev.rawY)) {
+                return
+            }
+
             if (isOnLeft && ev.rawX > screenCenterX) {
                 isOnLeft = false
                 dragListener.onLocationChanged(BubbleBarLocation.RIGHT)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java
index 8b4ac1a..d17e862 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java
@@ -107,7 +107,7 @@
                 DeviceStateManager.class);
         if (deviceStateManager != null) {
             deviceStateManager.registerCallback(mMainExecutor, state -> onDevicePostureChanged(
-                    mDeviceStateToPostureMap.get(state, DEVICE_POSTURE_UNKNOWN)));
+                    mDeviceStateToPostureMap.get(state.getIdentifier(), DEVICE_POSTURE_UNKNOWN)));
         }
     }
 
diff --git a/core/java/android/app/ondeviceintelligence/Content.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
similarity index 60%
copy from core/java/android/app/ondeviceintelligence/Content.aidl
copy to libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
index 40f0ef9..6781d08 100644
--- a/core/java/android/app/ondeviceintelligence/Content.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
@@ -1,11 +1,11 @@
-/**
- * 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.
  * You may obtain a copy of the License at
  *
- *     http://www.apache.org/licenses/LICENSE-2.0
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,9 +14,11 @@
  * limitations under the License.
  */
 
-package android.app.ondeviceintelligence;
+@file:JvmName("AppCompatUtils")
 
-/**
-  * @hide
-  */
-parcelable Content;
+package com.android.wm.shell.compatui
+
+import android.app.TaskInfo
+fun isSingleTopActivityTranslucent(task: TaskInfo) =
+    task.isTopActivityTransparent && task.numActivities == 1
+
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index fb0ed15..6a3c8d2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -138,9 +138,9 @@
             @WindowConfiguration.WindowingMode int windowingMode, int captionHeight) {
         final Region region = new Region();
         int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM
-                ? 2 * layout.stableInsets().top
-                : mContext.getResources().getDimensionPixelSize(
-                        com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height);
+                ? mContext.getResources().getDimensionPixelSize(
+                com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height)
+                : 2 * layout.stableInsets().top;
         // A thin, short Rect at the top of the screen.
         if (windowingMode == WINDOWING_MODE_FREEFORM) {
             int fromFreeformWidth = mContext.getResources().getDimensionPixelSize(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index dd8c1a0..992e5ae 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
@@ -19,6 +19,7 @@
 import android.app.ActivityManager.RunningTaskInfo
 import android.app.ActivityOptions
 import android.app.PendingIntent
+import android.app.TaskInfo
 import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
 import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
@@ -46,6 +47,7 @@
 import android.window.WindowContainerTransaction
 import androidx.annotation.BinderThread
 import com.android.internal.policy.ScreenDecorationsUtils
+import com.android.window.flags.Flags
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.common.DisplayController
@@ -63,6 +65,7 @@
 import com.android.wm.shell.common.annotations.ShellMainThread
 import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
 import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
+import com.android.wm.shell.compatui.isSingleTopActivityTranslucent
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
 import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener
 import com.android.wm.shell.draganddrop.DragAndDropController
@@ -78,8 +81,11 @@
 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 com.android.wm.shell.windowdecor.extension.isFreeform
+import com.android.wm.shell.windowdecor.extension.isFullscreen
 import java.io.PrintWriter
 import java.util.concurrent.Executor
 import java.util.function.Consumer
@@ -163,8 +169,11 @@
     private fun onInit() {
         KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController")
         shellCommandHandler.addDumpCallback(this::dump, this)
-        shellCommandHandler.addCommandCallback("desktopmode", desktopModeShellCommandHandler,
-            this)
+        shellCommandHandler.addCommandCallback(
+            "desktopmode",
+            desktopModeShellCommandHandler,
+            this
+        )
         shellController.addExternalInterface(
             ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE,
             { createExternalInterface() },
@@ -268,9 +277,11 @@
                     // Split-screen case where there are two focused tasks, then we find the child
                     // task to move to desktop.
                     val splitFocusedTask =
-                        if (allFocusedTasks[0].taskId == allFocusedTasks[1].parentTaskId)
+                        if (allFocusedTasks[0].taskId == allFocusedTasks[1].parentTaskId) {
                             allFocusedTasks[1]
-                        else allFocusedTasks[0]
+                        } else {
+                            allFocusedTasks[0]
+                        }
                     moveToDesktop(splitFocusedTask)
                 }
                 1 -> {
@@ -309,8 +320,18 @@
     ) {
         if (!DesktopModeStatus.canEnterDesktopMode(context)) {
             KtProtoLog.w(
-                WM_SHELL_DESKTOP_MODE, "DesktopTasksController: Cannot enter desktop, " +
-                        "display does not meet minimum size requirements")
+                WM_SHELL_DESKTOP_MODE,
+                "DesktopTasksController: Cannot enter desktop, " +
+                        "display does not meet minimum size requirements"
+            )
+            return
+        }
+        if (Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task)) {
+            KtProtoLog.w(
+                WM_SHELL_DESKTOP_MODE,
+                "DesktopTasksController: Cannot enter desktop, " +
+                        "translucent top activity found. This is likely a modal dialog."
+            )
             return
         }
         KtProtoLog.v(
@@ -445,7 +466,11 @@
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             exitDesktopTaskTransitionHandler.startTransition(
-            Transitions.TRANSIT_EXIT_DESKTOP_MODE, wct, position, mOnAnimationFinishedCallback)
+            Transitions.TRANSIT_EXIT_DESKTOP_MODE,
+                wct,
+                position,
+                mOnAnimationFinishedCallback
+            )
         } else {
             shellTaskOrganizer.applyTransaction(wct)
             releaseVisualIndicator()
@@ -491,8 +516,12 @@
             KtProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d not found", taskId)
             return
         }
-        KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d taskDisplayId=%d",
-                taskId, task.displayId)
+        KtProtoLog.v(
+            WM_SHELL_DESKTOP_MODE,
+            "moveToNextDisplay: taskId=%d taskDisplayId=%d",
+                taskId,
+            task.displayId
+        )
 
         val displayIds = rootTaskDisplayAreaOrganizer.displayIds.sorted()
         // Get the first display id that is higher than current task display id
@@ -514,8 +543,12 @@
      * No-op if task is already on that display per [RunningTaskInfo.displayId].
      */
     private fun moveToDisplay(task: RunningTaskInfo, displayId: Int) {
-        KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToDisplay: taskId=%d displayId=%d",
-                task.taskId, displayId)
+        KtProtoLog.v(
+            WM_SHELL_DESKTOP_MODE,
+            "moveToDisplay: taskId=%d displayId=%d",
+                task.taskId,
+            displayId
+        )
 
         if (task.displayId == displayId) {
             KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "moveToDisplay: task already on display")
@@ -589,7 +622,8 @@
         // Center the task in screen bounds
         outBounds.offset(
             screenBounds.centerX() - outBounds.centerX(),
-            screenBounds.centerY() - outBounds.centerY())
+            screenBounds.centerY() - outBounds.centerY()
+        )
     }
 
     private fun getSnapBounds(taskInfo: RunningTaskInfo, position: SnapPosition): Rect {
@@ -736,11 +770,13 @@
         val result = triggerTask?.let { task ->
             when {
                 // If display has tasks stashed, handle as stashed launch
-                desktopModeTaskRepository.isStashed(task.displayId) -> handleStashedTaskLaunch(task)
+                task.isStashed -> handleStashedTaskLaunch(task)
+                // Check if the task has a top transparent activity
+                shouldLaunchAsModal(task) -> handleTransparentTaskLaunch(task)
                 // Check if fullscreen task should be updated
-                task.windowingMode == WINDOWING_MODE_FULLSCREEN -> handleFullscreenTaskLaunch(task)
+                task.isFullscreen -> handleFullscreenTaskLaunch(task)
                 // Check if freeform task should be updated
-                task.windowingMode == WINDOWING_MODE_FREEFORM -> handleFreeformTaskLaunch(task)
+                task.isFreeform -> handleFreeformTaskLaunch(task)
                 else -> {
                     null
                 }
@@ -773,6 +809,13 @@
                 .forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) }
     }
 
+    private val TaskInfo.isStashed: Boolean
+        get() = desktopModeTaskRepository.isStashed(displayId)
+
+    private fun shouldLaunchAsModal(task: TaskInfo): Boolean {
+        return Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task)
+    }
+
     private fun handleFreeformTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? {
         KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFreeformTaskLaunch")
         val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId)
@@ -820,6 +863,16 @@
         return wct
     }
 
+    // Always launch transparent tasks in fullscreen.
+    private fun handleTransparentTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? {
+        // Already fullscreen, no-op.
+        if (task.isFullscreen)
+            return null
+        return WindowContainerTransaction().also { wct ->
+            addMoveToFullscreenChanges(wct, task)
+        }
+    }
+
     private fun addMoveToDesktopChanges(
         wct: WindowContainerTransaction,
         taskInfo: RunningTaskInfo
@@ -900,9 +953,12 @@
         ) {
             val wct = WindowContainerTransaction()
             addMoveToSplitChanges(wct, taskInfo)
-            splitScreenController.requestEnterSplitSelect(taskInfo, wct,
+            splitScreenController.requestEnterSplitSelect(
+                taskInfo,
+                wct,
                 if (leftOrTop) SPLIT_POSITION_TOP_OR_LEFT else SPLIT_POSITION_BOTTOM_OR_RIGHT,
-                taskInfo.configuration.windowConfiguration.bounds)
+                taskInfo.configuration.windowConfiguration.bounds
+            )
         }
     }
 
@@ -960,7 +1016,8 @@
     }
 
     /**
-     * Perform checks required on drag end. Move to fullscreen if drag ends in status bar area.
+     * Perform checks required on drag end. If indicator indicates a windowing mode change, perform
+     * that change. Otherwise, ensure bounds are up to date.
      *
      * @param taskInfo the task being dragged.
      * @param position position of surface when drag ends.
@@ -971,7 +1028,8 @@
         taskInfo: RunningTaskInfo,
         position: Point,
         inputCoordinate: PointF,
-        taskBounds: Rect
+        taskBounds: Rect,
+        validDragArea: Rect
     ) {
         if (taskInfo.configuration.windowConfiguration.windowingMode != WINDOWING_MODE_FREEFORM) {
             return
@@ -994,10 +1052,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()
@@ -1097,7 +1166,8 @@
             pendingIntentLaunchFlags =
                 Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK
             setPendingIntentBackgroundActivityStartMode(
-                ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+                ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+            )
             isPendingIntentBackgroundActivityLaunchAllowedByPermission = true
         }
         val wct = WindowContainerTransaction()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index ad40493..b99e943 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -64,7 +64,9 @@
         default void onSplitVisibilityChanged(boolean visible) {}
     }
 
-    /** Callback interface for listening to requests to enter split select */
+    /**
+     * Callback interface for listening to requests to enter split select. Used for desktop -> split
+     */
     interface SplitSelectListener {
         default boolean onRequestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
                 int splitPosition, Rect taskBounds) {
@@ -72,6 +74,15 @@
         }
     }
 
+    interface SplitInvocationListener {
+        /**
+         * Called whenever shell starts or stops the split screen animation
+         * @param animationRunning if {@code true} the animation has begun, if {@code false} the
+         *                         animation has finished
+         */
+        default void onSplitAnimationInvoked(boolean animationRunning) { }
+    }
+
     /** Registers listener that gets split screen callback. */
     void registerSplitScreenListener(@NonNull SplitScreenListener listener,
             @NonNull Executor executor);
@@ -79,12 +90,24 @@
     /** Unregisters listener that gets split screen callback. */
     void unregisterSplitScreenListener(@NonNull SplitScreenListener listener);
 
+    /**
+     * Registers a {@link SplitInvocationListener} to notify when the animation to enter split
+     * screen has started and stopped
+     *
+     * @param executor callbacks to the listener will be executed on this executor
+     */
+    void registerSplitAnimationListener(@NonNull SplitInvocationListener listener,
+            @NonNull Executor executor);
+
     /** Called when device waking up finished. */
     void onFinishedWakingUp();
 
     /** Called when requested to go to fullscreen from the current active split app. */
     void goToFullscreenFromSplit();
 
+    /** Called when splitscreen focused app is changed. */
+    void setSplitscreenFocus(boolean leftOrTop);
+
     /** Get a string representation of a stage type */
     static String stageTypeToString(@StageType int stage) {
         switch (stage) {
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..5fbb152 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
@@ -481,6 +485,12 @@
         }
     }
 
+    public void setSplitscreenFocus(boolean leftOrTop) {
+        if (mStageCoordinator.isSplitActive()) {
+            mStageCoordinator.grantFocusToPosition(leftOrTop);
+        }
+    }
+
     /** Move the specified task to fullscreen, regardless of focus state. */
     public void moveTaskToFullscreen(int taskId, int exitReason) {
         mStageCoordinator.moveTaskToFullscreen(taskId, exitReason);
@@ -1134,6 +1144,12 @@
         }
 
         @Override
+        public void registerSplitAnimationListener(@NonNull SplitInvocationListener listener,
+                @NonNull Executor executor) {
+            mStageCoordinator.registerSplitAnimationListener(listener, executor);
+        }
+
+        @Override
         public void onFinishedWakingUp() {
             mMainExecutor.execute(SplitScreenController.this::onFinishedWakingUp);
         }
@@ -1142,6 +1158,12 @@
         public void goToFullscreenFromSplit() {
             mMainExecutor.execute(SplitScreenController.this::goToFullscreenFromSplit);
         }
+
+        @Override
+        public void setSplitscreenFocus(boolean leftOrTop) {
+            mMainExecutor.execute(
+                    () -> SplitScreenController.this.setSplitscreenFocus(leftOrTop));
+        }
     }
 
     /**
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/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 1a53a1d..e69ff70 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -55,6 +55,7 @@
 import com.android.wm.shell.transition.Transitions;
 
 import java.util.ArrayList;
+import java.util.concurrent.Executor;
 
 /** Manages transition animations for split-screen. */
 class SplitScreenTransitions {
@@ -79,6 +80,8 @@
 
     private Transitions.TransitionFinishCallback mFinishCallback = null;
     private SurfaceControl.Transaction mFinishTransaction;
+    private SplitScreen.SplitInvocationListener mSplitInvocationListener;
+    private Executor mSplitInvocationListenerExecutor;
 
     SplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions,
             @NonNull Runnable onFinishCallback, StageCoordinator stageCoordinator) {
@@ -353,6 +356,10 @@
                     + " skip to start enter split transition since it already exist. ");
             return null;
         }
+        if (mSplitInvocationListenerExecutor != null && mSplitInvocationListener != null) {
+            mSplitInvocationListenerExecutor.execute(() -> mSplitInvocationListener
+                    .onSplitAnimationInvoked(true /*animationRunning*/));
+        }
         final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
         setEnterTransition(transition, remoteTransition, extraTransitType, resizeAnim);
         return transition;
@@ -529,6 +536,12 @@
         mTransitions.getAnimExecutor().execute(va::start);
     }
 
+    public void registerSplitAnimListener(@NonNull SplitScreen.SplitInvocationListener listener,
+            @NonNull Executor executor) {
+        mSplitInvocationListener = listener;
+        mSplitInvocationListenerExecutor = executor;
+    }
+
     /** Calls when the transition got consumed. */
     interface TransitionConsumedCallback {
         void onConsumed(boolean aborted);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index ec907fd..661faf5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -156,6 +156,7 @@
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.Executor;
 
 /**
  * Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and
@@ -236,6 +237,9 @@
     private DefaultMixedHandler mMixedHandler;
     private final Toast mSplitUnsupportedToast;
     private SplitRequest mSplitRequest;
+    /** Used to notify others of when shell is animating into split screen */
+    private SplitScreen.SplitInvocationListener mSplitInvocationListener;
+    private Executor mSplitInvocationListenerExecutor;
 
     /**
      * Since StageCoordinator only coordinates MainStage and SideStage, it shouldn't support
@@ -246,6 +250,14 @@
         return false;
     }
 
+    /** NOTE: Will overwrite any previously set {@link #mSplitInvocationListener} */
+    public void registerSplitAnimationListener(
+            @NonNull SplitScreen.SplitInvocationListener listener, @NonNull Executor executor) {
+        mSplitInvocationListener = listener;
+        mSplitInvocationListenerExecutor = executor;
+        mSplitTransitions.registerSplitAnimListener(listener, executor);
+    }
+
     class SplitRequest {
         @SplitPosition
         int mActivatePosition;
@@ -529,7 +541,7 @@
                             null /* childrenToTop */, EXIT_REASON_UNKNOWN));
                     Log.w(TAG, splitFailureMessage("startShortcut",
                             "side stage was not populated"));
-                    mSplitUnsupportedToast.show();
+                    handleUnsupportedSplitStart();
                 }
 
                 if (finishedCallback != null) {
@@ -660,7 +672,7 @@
                             null /* childrenToTop */, EXIT_REASON_UNKNOWN));
                     Log.w(TAG, splitFailureMessage("startIntentLegacy",
                             "side stage was not populated"));
-                    mSplitUnsupportedToast.show();
+                    handleUnsupportedSplitStart();
                 }
 
                 if (apps != null) {
@@ -1213,7 +1225,7 @@
                             ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
             Log.w(TAG, splitFailureMessage("onRemoteAnimationFinishedOrCancelled",
                     "main or side stage was not populated."));
-            mSplitUnsupportedToast.show();
+            handleUnsupportedSplitStart();
         } else {
             mSyncQueue.queue(evictWct);
             mSyncQueue.runInSync(t -> {
@@ -1234,7 +1246,7 @@
                     ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
             Log.w(TAG, splitFailureMessage("onRemoteAnimationFinished",
                     "main or side stage was not populated"));
-            mSplitUnsupportedToast.show();
+            handleUnsupportedSplitStart();
             return;
         }
 
@@ -1451,6 +1463,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());
@@ -1470,6 +1483,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",
@@ -1547,6 +1561,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.
      */
@@ -1582,6 +1604,11 @@
         }
     }
 
+    protected void grantFocusToPosition(boolean leftOrTop) {
+        grantFocusToStage(mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
+                ? getMainStagePosition() : getSideStagePosition());
+    }
+
     private void clearRequestIfPresented() {
         ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "clearRequestIfPresented");
         if (mSideStageListener.mVisible && mSideStageListener.mHasChildren
@@ -1612,6 +1639,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;
@@ -2789,6 +2818,7 @@
             if (hasEnteringPip) {
                 mMixedHandler.animatePendingEnterPipFromSplit(transition, info,
                         startTransaction, finishTransaction, finishCallback);
+                notifySplitAnimationFinished();
                 return true;
             }
 
@@ -2823,6 +2853,7 @@
                 //                    the transition, or synchronize task-org callbacks.
             }
             // Use normal animations.
+            notifySplitAnimationFinished();
             return false;
         } else if (mMixedHandler != null && TransitionUtil.hasDisplayChange(info)) {
             // A display-change has been un-expectedly inserted into the transition. Redirect
@@ -2836,6 +2867,7 @@
                     mSplitLayout.update(startTransaction, true /* resetImePosition */);
                     startTransaction.apply();
                 }
+                notifySplitAnimationFinished();
                 return true;
             }
         }
@@ -3009,7 +3041,7 @@
                     pendingEnter.mRemoteHandler.onTransitionConsumed(transition,
                             false /*aborted*/, finishT);
                 }
-                mSplitUnsupportedToast.show();
+                handleUnsupportedSplitStart();
                 return true;
             }
         }
@@ -3038,6 +3070,7 @@
         final TransitionInfo.Change finalMainChild = mainChild;
         final TransitionInfo.Change finalSideChild = sideChild;
         enterTransition.setFinishedCallback((callbackWct, callbackT) -> {
+            notifySplitAnimationFinished();
             if (finalMainChild != null) {
                 if (!mainNotContainOpenTask) {
                     mMainStage.evictOtherChildren(callbackWct, finalMainChild.getTaskInfo().taskId);
@@ -3454,6 +3487,19 @@
                 mSplitLayout.isLeftRightSplit());
     }
 
+    private void handleUnsupportedSplitStart() {
+        mSplitUnsupportedToast.show();
+        notifySplitAnimationFinished();
+    }
+
+    private void notifySplitAnimationFinished() {
+        if (mSplitInvocationListener == null || mSplitInvocationListenerExecutor == null) {
+            return;
+        }
+        mSplitInvocationListenerExecutor.execute(() ->
+                mSplitInvocationListener.onSplitAnimationInvoked(false /*animationRunning*/));
+    }
+
     /**
      * Logs the exit of splitscreen to a specific stage. This must be called before the exit is
      * executed.
@@ -3516,7 +3562,7 @@
                 if (!ENABLE_SHELL_TRANSITIONS) {
                     StageCoordinator.this.exitSplitScreen(isMainStage ? mMainStage : mSideStage,
                             EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
-                    mSplitUnsupportedToast.show();
+                    handleUnsupportedSplitStart();
                     return;
                 }
 
@@ -3536,7 +3582,7 @@
                         "app package " + taskInfo.baseActivity.getPackageName()
                         + " does not support splitscreen, or is a controlled activity type"));
                 if (splitScreenVisible) {
-                    mSplitUnsupportedToast.show();
+                    handleUnsupportedSplitStart();
                 }
             }
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index 1ce87ef..4465aef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -18,6 +18,7 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.window.StartingWindowRemovalInfo.DEFER_MODE_NONE;
 import static android.window.StartingWindowRemovalInfo.DEFER_MODE_NORMAL;
 import static android.window.StartingWindowRemovalInfo.DEFER_MODE_ROTATION;
 
@@ -270,21 +271,18 @@
 
         @Override
         public final boolean removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
-            if (immediately) {
+            if (immediately
+                    // Show the latest content as soon as possible for unlocking to home.
+                    || mActivityType == ACTIVITY_TYPE_HOME
+                    || info.deferRemoveMode == DEFER_MODE_NONE) {
                 removeImmediately();
-            } else {
-                scheduleRemove(info.deferRemoveForImeMode);
-                return false;
+                return true;
             }
-            return true;
+            scheduleRemove(info.deferRemoveMode);
+            return false;
         }
 
         void scheduleRemove(@StartingWindowRemovalInfo.DeferMode int deferRemoveForImeMode) {
-            // Show the latest content as soon as possible for unlocking to home.
-            if (mActivityType == ACTIVITY_TYPE_HOME) {
-                removeImmediately();
-                return;
-            }
             mRemoveExecutor.removeCallbacks(mScheduledRunnable);
             final long delayRemovalTime;
             switch (deferRemoveForImeMode) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
index 4ea71490..5b402a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
@@ -212,6 +212,7 @@
         switch (mType) {
             case TYPE_RECENTS_DURING_DESKTOP:
             case TYPE_RECENTS_DURING_SPLIT:
+            case TYPE_RECENTS_DURING_KEYGUARD:
                 mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
                 break;
             default:
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
index 7a50814..564e716 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
@@ -31,42 +31,42 @@
     companion object {
         /** @see [com.android.internal.protolog.common.ProtoLog.d] */
         fun d(group: IProtoLogGroup, messageString: String, vararg args: Any) {
-            if (ProtoLog.isEnabled(group)) {
+            if (group.isLogToLogcat) {
                 Log.d(group.tag, String.format(messageString, *args))
             }
         }
 
         /** @see [com.android.internal.protolog.common.ProtoLog.v] */
         fun v(group: IProtoLogGroup, messageString: String, vararg args: Any) {
-            if (ProtoLog.isEnabled(group)) {
+            if (group.isLogToLogcat) {
                 Log.v(group.tag, String.format(messageString, *args))
             }
         }
 
         /** @see [com.android.internal.protolog.common.ProtoLog.i] */
         fun i(group: IProtoLogGroup, messageString: String, vararg args: Any) {
-            if (ProtoLog.isEnabled(group)) {
+            if (group.isLogToLogcat) {
                 Log.i(group.tag, String.format(messageString, *args))
             }
         }
 
         /** @see [com.android.internal.protolog.common.ProtoLog.w] */
         fun w(group: IProtoLogGroup, messageString: String, vararg args: Any) {
-            if (ProtoLog.isEnabled(group)) {
+            if (group.isLogToLogcat) {
                 Log.w(group.tag, String.format(messageString, *args))
             }
         }
 
         /** @see [com.android.internal.protolog.common.ProtoLog.e] */
         fun e(group: IProtoLogGroup, messageString: String, vararg args: Any) {
-            if (ProtoLog.isEnabled(group)) {
+            if (group.isLogToLogcat) {
                 Log.e(group.tag, String.format(messageString, *args))
             }
         }
 
         /** @see [com.android.internal.protolog.common.ProtoLog.wtf] */
         fun wtf(group: IProtoLogGroup, messageString: String, vararg args: Any) {
-            if (ProtoLog.isEnabled(group)) {
+            if (group.isLogToLogcat) {
                 Log.wtf(group.tag, String.format(messageString, *args))
             }
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index b2eeea7..c59a1b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -19,9 +19,11 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowManager.TRANSIT_CHANGE;
 
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
+import android.graphics.Rect;
 import android.os.Handler;
 import android.util.SparseArray;
 import android.view.Choreographer;
@@ -186,7 +188,7 @@
 
         final FluidResizeTaskPositioner taskPositioner =
                 new FluidResizeTaskPositioner(mTaskOrganizer, mTransitions, windowDecoration,
-                        mDisplayController, 0 /* disallowedAreaForEndBoundsHeight */);
+                        mDisplayController);
         final CaptionTouchEventListener touchEventListener =
                 new CaptionTouchEventListener(taskInfo, taskPositioner);
         windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
@@ -286,8 +288,15 @@
                         mDragPointerId = e.getPointerId(0);
                     }
                     final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
-                    mDragPositioningCallback.onDragPositioningEnd(
+                    final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningEnd(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
+                    DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(newTaskBounds,
+                            mWindowDecorByTaskId.get(mTaskId).calculateValidDragArea());
+                    if (newTaskBounds != taskInfo.configuration.windowConfiguration.getBounds()) {
+                        final WindowContainerTransaction wct = new WindowContainerTransaction();
+                        wct.setBounds(taskInfo.token, newTaskBounds);
+                        mTransitions.startTransition(TRANSIT_CHANGE, wct, null);
+                    }
                     final boolean wasDragging = mIsDragging;
                     mIsDragging = false;
                     return wasDragging;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 91e9601..9a48922 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.windowdecor;
 
+import android.annotation.NonNull;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.WindowConfiguration;
 import android.app.WindowConfiguration.WindowingMode;
@@ -87,6 +88,7 @@
     }
 
     @Override
+    @NonNull
     Rect calculateValidDragArea() {
         final int leftButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
                 R.dimen.caption_left_buttons_width);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 98ff0ee..6d2109c 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
@@ -30,6 +30,7 @@
 
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.compatui.AppCompatUtils.isSingleTopActivityTranslucent;
 import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR;
 import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR;
 import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR;
@@ -75,6 +76,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
 import com.android.wm.shell.R;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
@@ -639,7 +641,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 +869,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;
                     }
@@ -1054,6 +1057,10 @@
                 && taskInfo.isFocused) {
             return false;
         }
+        if (Flags.enableDesktopWindowingModalsPolicy()
+                && isSingleTopActivityTranslucent(taskInfo)) {
+            return false;
+        }
         return DesktopModeStatus.isEnabled()
                 && taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED
                 && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
@@ -1086,18 +1093,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/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
index 7a64a47..a2293d5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
@@ -17,6 +17,8 @@
 package com.android.wm.shell.windowdecor.extension
 
 import android.app.TaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
 import android.view.WindowInsetsController.APPEARANCE_LIGHT_CAPTION_BARS
 import android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND
 
@@ -31,3 +33,9 @@
         val appearance = taskDescription?.systemBarsAppearance ?: 0
         return (appearance and APPEARANCE_LIGHT_CAPTION_BARS) != 0
     }
+
+val TaskInfo.isFullscreen: Boolean
+    get() = windowingMode == WINDOWING_MODE_FULLSCREEN
+
+val TaskInfo.isFreeform: Boolean
+    get() = windowingMode == WINDOWING_MODE_FREEFORM
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
new file mode 100644
index 0000000..4cd2a36
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.compatui
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for {@link AppCompatUtils}.
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:AppCompatUtilsTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class AppCompatUtilsTest : ShellTestCase() {
+
+    @Test
+    fun testIsSingleTopActivityTranslucent() {
+        assertTrue(isSingleTopActivityTranslucent(
+            createFreeformTask(/* displayId */ 0)
+                    .apply {
+                        isTopActivityTransparent = true
+                        numActivities = 1
+                    }))
+        assertFalse(isSingleTopActivityTranslucent(
+            createFreeformTask(/* displayId */ 0)
+                    .apply {
+                        isTopActivityTransparent = true
+                        numActivities = 0
+                    }))
+        assertFalse(isSingleTopActivityTranslucent(
+            createFreeformTask(/* displayId */ 0)
+                    .apply {
+                        isTopActivityTransparent = false
+                        numActivities = 1
+                    }))
+    }
+}
\ 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 5df9dd3..254bf7d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -23,9 +23,14 @@
 import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
 import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
 import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
+import android.graphics.Point
+import android.graphics.PointF
+import android.graphics.Rect
 import android.os.Binder
+import android.platform.test.flag.junit.SetFlagsRule
 import android.testing.AndroidTestingRunner
 import android.view.Display.DEFAULT_DISPLAY
+import android.view.SurfaceControl
 import android.view.WindowManager
 import android.view.WindowManager.TRANSIT_CHANGE
 import android.view.WindowManager.TRANSIT_OPEN
@@ -40,14 +45,15 @@
 import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
 import com.android.dx.mockito.inline.extended.ExtendedMockito.never
 import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.window.flags.Flags
 import com.android.wm.shell.MockToken
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.transition.TestRemoteTransition
 import com.android.wm.shell.TestRunningTaskInfoBuilder
 import com.android.wm.shell.TestShellExecutor
 import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayLayout
 import com.android.wm.shell.common.LaunchAdjacentController
 import com.android.wm.shell.common.MultiInstanceHelper
 import com.android.wm.shell.common.ShellExecutor
@@ -65,16 +71,17 @@
 import com.android.wm.shell.sysui.ShellController
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.OneShotRemoteHandler
+import com.android.wm.shell.transition.TestRemoteTransition
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS
 import com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_DESKTOP_MODE
 import com.android.wm.shell.transition.Transitions.TransitionHandler
-import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import org.junit.After
 import org.junit.Assume.assumeTrue
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
@@ -86,15 +93,25 @@
 import org.mockito.Mockito.any
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
 import org.mockito.kotlin.times
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.quality.Strictness
 
+/**
+ * Test class for {@link DesktopTasksController}
+ *
+ * Usage: atest WMShellUnitTests:DesktopTasksControllerTest
+ */
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 class DesktopTasksControllerTest : ShellTestCase() {
 
+    @JvmField
+    @Rule
+    val setFlagsRule = SetFlagsRule()
+
     @Mock lateinit var testExecutor: ShellExecutor
     @Mock lateinit var shellCommandHandler: ShellCommandHandler
     @Mock lateinit var shellController: ShellController
@@ -109,7 +126,6 @@
             ToggleResizeDesktopTaskTransitionHandler
     @Mock lateinit var dragToDesktopTransitionHandler: DragToDesktopTransitionHandler
     @Mock lateinit var launchAdjacentController: LaunchAdjacentController
-    @Mock lateinit var desktopModeWindowDecoration: DesktopModeWindowDecoration
     @Mock lateinit var splitScreenController: SplitScreenController
     @Mock lateinit var recentsTransitionHandler: RecentsTransitionHandler
     @Mock lateinit var dragAndDropController: DragAndDropController
@@ -123,6 +139,7 @@
     private lateinit var recentsTransitionStateListener: RecentsTransitionStateListener
 
     private val shellExecutor = TestShellExecutor()
+
     // Mock running tasks are registered here so we can get the list from mock shell task organizer
     private val runningTasks = mutableListOf<RunningTaskInfo>()
 
@@ -350,6 +367,18 @@
     }
 
     @Test
+    fun moveToDesktop_topActivityTranslucent_doesNothing() {
+        setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+        val task = setUpFullscreenTask().apply {
+            isTopActivityTransparent = true
+            numActivities = 1
+        }
+
+        controller.moveToDesktop(task)
+        verifyWCTNotExecuted()
+    }
+
+    @Test
     fun moveToDesktop_deviceNotSupported_deviceRestrictionsOverridden_taskIsMovedToDesktop() {
         val task = setUpFullscreenTask()
 
@@ -424,7 +453,9 @@
         val wct = getLatestMoveToDesktopWct()
         assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
             .isEqualTo(WINDOWING_MODE_FREEFORM)
-        verify(splitScreenController).prepareExitSplitScreen(any(), anyInt(),
+        verify(splitScreenController).prepareExitSplitScreen(
+            any(),
+            anyInt(),
             eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE)
         )
     }
@@ -436,7 +467,9 @@
         val wct = getLatestMoveToDesktopWct()
         assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
             .isEqualTo(WINDOWING_MODE_FREEFORM)
-        verify(splitScreenController, never()).prepareExitSplitScreen(any(), anyInt(),
+        verify(splitScreenController, never()).prepareExitSplitScreen(
+            any(),
+            anyInt(),
             eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE)
         )
     }
@@ -744,6 +777,19 @@
     }
 
     @Test
+    fun handleRequest_shouldLaunchAsModal_returnSwitchToFullscreenWCT() {
+        setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+        val task = setUpFreeformTask().apply {
+            isTopActivityTransparent = true
+            numActivities = 1
+        }
+
+        val result = controller.handleRequest(Binder(), createTransition(task))
+        assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+                .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+    }
+
+    @Test
     fun stashDesktopApps_stateUpdates() {
         whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
 
@@ -822,7 +868,9 @@
         val wct = getLatestMoveToDesktopWct()
         assertThat(wct.changes[task4.token.asBinder()]?.windowingMode)
                 .isEqualTo(WINDOWING_MODE_FREEFORM)
-        verify(splitScreenController).prepareExitSplitScreen(any(), anyInt(),
+        verify(splitScreenController).prepareExitSplitScreen(
+            any(),
+            anyInt(),
             eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE)
         )
     }
@@ -844,6 +892,31 @@
                 .isEqualTo(WINDOWING_MODE_FULLSCREEN)
     }
 
+    @Test
+    fun onDesktopDragMove_endsOutsideValidDragArea_snapsToValidBounds() {
+        val task = setUpFreeformTask()
+        val mockSurface = mock(SurfaceControl::class.java)
+        val mockDisplayLayout = mock(DisplayLayout::class.java)
+        whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout)
+        whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000))
+        controller.onDragPositioningMove(task, mockSurface, 200f,
+            Rect(100, -100, 500, 1000))
+
+        controller.onDragPositioningEnd(task,
+            Point(100, -100), /* position */
+            PointF(200f, -200f), /* inputCoordinate */
+            Rect(100, -100, 500, 1000), /* taskBounds */
+            Rect(0, 50, 2000, 2000) /* validDragArea */
+        )
+        val rectAfterEnd = Rect(100, 50, 500, 1150)
+        verify(transitions).startTransition(
+            eq(TRANSIT_CHANGE), Mockito.argThat { wct ->
+                return@argThat wct.changes.any { (token, change) ->
+                    change.configuration.windowConfiguration.bounds == rectAfterEnd
+                }
+            }, eq(null))
+    }
+
     fun enterSplit_freeformTaskIsMovedToSplit() {
         val task1 = setUpFreeformTask()
         val task2 = setUpFreeformTask()
@@ -855,9 +928,12 @@
 
         controller.enterSplit(DEFAULT_DISPLAY, false)
 
-        verify(splitScreenController).requestEnterSplitSelect(task2, any(),
+        verify(splitScreenController).requestEnterSplitSelect(
+            task2,
+            any(),
             SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT,
-            task2.configuration.windowConfiguration.bounds)
+            task2.configuration.windowConfiguration.bounds
+        )
     }
 
     private fun setUpFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index befc702..34b2eeb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -39,10 +39,13 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
@@ -63,6 +66,7 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.TestShellExecutor;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
@@ -105,6 +109,8 @@
     @Mock private ShellExecutor mMainExecutor;
     @Mock private LaunchAdjacentController mLaunchAdjacentController;
     @Mock private DefaultMixedHandler mMixedHandler;
+    @Mock private SplitScreen.SplitInvocationListener mInvocationListener;
+    private final TestShellExecutor mTestShellExecutor = new TestShellExecutor();
     private SplitLayout mSplitLayout;
     private MainStage mMainStage;
     private SideStage mSideStage;
@@ -147,6 +153,7 @@
                 .setParentTaskId(mSideStage.mRootTaskInfo.taskId).build();
         doReturn(mock(SplitDecorManager.class)).when(mMainStage).getSplitDecorManager();
         doReturn(mock(SplitDecorManager.class)).when(mSideStage).getSplitDecorManager();
+        mStageCoordinator.registerSplitAnimationListener(mInvocationListener, mTestShellExecutor);
     }
 
     @Test
@@ -452,6 +459,15 @@
         mMainStage.activate(new WindowContainerTransaction(), true /* includingTopTask */);
     }
 
+    @Test
+    @UiThreadTest
+    public void testSplitInvocationCallback() {
+        enterSplit();
+        mTestShellExecutor.flushAll();
+        verify(mInvocationListener, times(1))
+                .onSplitAnimationInvoked(eq(true));
+    }
+
     private boolean containsSplitEnter(@NonNull WindowContainerTransaction wct) {
         for (int i = 0; i < wct.getHierarchyOps().size(); ++i) {
             WindowContainerTransaction.HierarchyOp op = wct.getHierarchyOps().get(i);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 6940739..8e9619d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -44,8 +44,8 @@
 import android.view.WindowInsets.Type.navigationBars
 import android.view.WindowInsets.Type.statusBars
 import androidx.test.filters.SmallTest
-import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
 import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
 import com.android.dx.mockito.inline.extended.StaticMockitoSession
 import com.android.window.flags.Flags
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
@@ -65,11 +65,14 @@
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener
+import java.util.Optional
+import java.util.function.Supplier
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
@@ -78,15 +81,14 @@
 import org.mockito.kotlin.any
 import org.mockito.kotlin.argumentCaptor
 import org.mockito.kotlin.eq
+import org.mockito.kotlin.spy
 import org.mockito.kotlin.whenever
 import org.mockito.quality.Strictness
-import java.util.Optional
-import java.util.function.Supplier
-import org.mockito.Mockito
-import org.mockito.kotlin.spy
 
-
-/** Tests of [DesktopModeWindowDecorViewModel]  */
+/**
+ * Tests of [DesktopModeWindowDecorViewModel]
+ * Usage: atest WMShellUnitTests:DesktopModeWindowDecorViewModelTests
+ */
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper
@@ -291,6 +293,19 @@
     }
 
     @Test
+    fun testDescorationIsNotCreatedForTopTranslucentActivities() {
+        setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+        val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true).apply {
+            isTopActivityTransparent = true
+            numActivities = 1
+        }
+        onTaskOpening(task)
+
+        verify(mockDesktopModeWindowDecorFactory, never())
+                .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+    }
+
+    @Test
     fun testRelayoutRunsWhenStatusBarsInsetsSourceVisibilityChanges() {
         val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true)
         val decoration = setUpMockDecorationForTask(task)
@@ -470,7 +485,8 @@
 
     private fun setUpMockDecorationForTask(task: RunningTaskInfo): DesktopModeWindowDecoration {
         val decoration = mock(DesktopModeWindowDecoration::class.java)
-        whenever(mockDesktopModeWindowDecorFactory.create(
+        whenever(
+            mockDesktopModeWindowDecorFactory.create(
                 any(), any(), any(), eq(task), any(), any(), any(), any(), any())
         ).thenReturn(decoration)
         decoration.mTaskInfo = task
@@ -489,7 +505,7 @@
                 "testEventReceiversOnMultipleDisplays",
                 /*width=*/ 400,
                 /*height=*/ 400,
-                /*densityDpi=*/320,
+                /*densityDpi=*/ 320,
                 surfaceView.holder.surface,
                 DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
         )
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/Android.bp b/libs/hwui/Android.bp
index 4486f55..33830f1 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -563,6 +563,7 @@
         "FrameInfoVisualizer.cpp",
         "FrameMetricsReporter.cpp",
         "Gainmap.cpp",
+        "HWUIProperties.sysprop",
         "Interpolator.cpp",
         "JankTracker.cpp",
         "LightingInfo.cpp",
@@ -628,7 +629,6 @@
                 "AutoBackendTextureRelease.cpp",
                 "DeferredLayerUpdater.cpp",
                 "HardwareBitmapUploader.cpp",
-                "HWUIProperties.sysprop",
                 "Layer.cpp",
                 "LayerUpdateQueue.cpp",
                 "ProfileDataContainer.cpp",
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/Properties.cpp b/libs/hwui/Properties.cpp
index 755332ff..325bdd6 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -16,10 +16,6 @@
 
 #include "Properties.h"
 
-#include "Debug.h"
-#ifdef __ANDROID__
-#include "HWUIProperties.sysprop.h"
-#endif
 #include <android-base/properties.h>
 #include <cutils/compiler.h>
 #include <log/log.h>
@@ -28,6 +24,8 @@
 #include <cstdlib>
 #include <optional>
 
+#include "Debug.h"
+#include "HWUIProperties.sysprop.h"
 #include "src/core/SkTraceEventCommon.h"
 
 #ifdef __ANDROID__
@@ -47,16 +45,6 @@
 namespace android {
 namespace uirenderer {
 
-#ifndef __ANDROID__ // Layoutlib does not compile HWUIProperties.sysprop as it depends on cutils properties
-std::optional<bool> use_vulkan() {
-    return base::GetBoolProperty("ro.hwui.use_vulkan", true);
-}
-
-std::optional<std::int32_t> render_ahead() {
-    return base::GetIntProperty("ro.hwui.render_ahead", 0);
-}
-#endif
-
 bool Properties::debugLayersUpdates = false;
 bool Properties::debugOverdraw = false;
 bool Properties::debugTraceGpuResourceCategories = false;
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 54aef55..d026379 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -573,9 +573,9 @@
 struct DrawMesh final : Op {
     static const auto kType = Type::DrawMesh;
     DrawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint)
-            : mesh(mesh), blender(std::move(blender)), paint(paint) {}
+            : mesh(mesh.takeSnapshot()), blender(std::move(blender)), paint(paint) {}
 
-    const Mesh& mesh;
+    Mesh::Snapshot mesh;
     sk_sp<SkBlender> blender;
     SkPaint paint;
 
@@ -1296,14 +1296,5 @@
     fDL->drawWebView(drawable);
 }
 
-[[nodiscard]] const SkMesh& DrawMeshPayload::getSkMesh() const {
-    LOG_FATAL_IF(!meshWrapper && !mesh, "One of Mesh or Mesh must be non-null");
-    if (meshWrapper) {
-        return meshWrapper->getSkMesh();
-    } else {
-        return *mesh;
-    }
-}
-
 }  // namespace uirenderer
 }  // namespace android
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 965264f..f867852 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -41,11 +41,12 @@
 
 enum class SkBlendMode;
 class SkRRect;
-class Mesh;
 
 namespace android {
-namespace uirenderer {
 
+class Mesh;
+
+namespace uirenderer {
 namespace skiapipeline {
 class FunctorDrawable;
 }
@@ -68,18 +69,6 @@
 
 static_assert(sizeof(DisplayListOp) == 4);
 
-class DrawMeshPayload {
-public:
-    explicit DrawMeshPayload(const SkMesh* mesh) : mesh(mesh) {}
-    explicit DrawMeshPayload(const Mesh* meshWrapper) : meshWrapper(meshWrapper) {}
-
-    [[nodiscard]] const SkMesh& getSkMesh() const;
-
-private:
-    const SkMesh* mesh = nullptr;
-    const Mesh* meshWrapper = nullptr;
-};
-
 struct DrawImagePayload {
     explicit DrawImagePayload(Bitmap& bitmap)
             : image(bitmap.makeImage()), palette(bitmap.palette()) {
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 0b739c3..72e83af 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -596,8 +596,8 @@
     if (recordingContext) {
         context = recordingContext->asDirectContext();
     }
-    mesh.updateSkMesh(context);
-    mCanvas->drawMesh(mesh.getSkMesh(), blender, paint);
+    mesh.refBufferData()->updateBuffers(context);
+    mCanvas->drawMesh(mesh.takeSnapshot().getSkMesh(), blender, paint);
 }
 
 // ----------------------------------------------------------------------------
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 14b4f58..4eb6918 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -34,7 +34,6 @@
 class SkRRect;
 class SkRuntimeShaderBuilder;
 class SkVertices;
-class Mesh;
 
 namespace minikin {
 class Font;
@@ -61,6 +60,7 @@
 
 class AnimatedImageDrawable;
 class Bitmap;
+class Mesh;
 class Paint;
 struct Typeface;
 
diff --git a/libs/hwui/jni/android_graphics_Mesh.cpp b/libs/hwui/jni/android_graphics_Mesh.cpp
index 5cb43e5..3109de5 100644
--- a/libs/hwui/jni/android_graphics_Mesh.cpp
+++ b/libs/hwui/jni/android_graphics_Mesh.cpp
@@ -38,8 +38,8 @@
         return 0;
     }
     auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
-    auto meshPtr = new Mesh(skMeshSpec, mode, std::move(buffer), vertexCount, vertexOffset,
-                            std::make_unique<MeshUniformBuilder>(skMeshSpec), skRect);
+    auto meshPtr = new Mesh(skMeshSpec, static_cast<SkMesh::Mode>(mode), std::move(buffer),
+                            vertexCount, vertexOffset, skRect);
     auto [valid, msg] = meshPtr->validate();
     if (!valid) {
         jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", msg.c_str());
@@ -63,9 +63,9 @@
         return 0;
     }
     auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
-    auto meshPtr = new Mesh(skMeshSpec, mode, std::move(vBuf), vertexCount, vertexOffset,
-                            std::move(iBuf), indexCount, indexOffset,
-                            std::make_unique<MeshUniformBuilder>(skMeshSpec), skRect);
+    auto meshPtr =
+            new Mesh(skMeshSpec, static_cast<SkMesh::Mode>(mode), std::move(vBuf), vertexCount,
+                     vertexOffset, std::move(iBuf), indexCount, indexOffset, skRect);
     auto [valid, msg] = meshPtr->validate();
     if (!valid) {
         jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", msg.c_str());
@@ -133,7 +133,6 @@
     ScopedUtfChars name(env, uniformName);
     const float values[4] = {value1, value2, value3, value4};
     nativeUpdateFloatUniforms(env, wrapper->uniformBuilder(), name.c_str(), values, count, false);
-    wrapper->markDirty();
 }
 
 static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring jUniformName,
@@ -143,7 +142,6 @@
     AutoJavaFloatArray autoValues(env, jvalues, 0, kRO_JNIAccess);
     nativeUpdateFloatUniforms(env, wrapper->uniformBuilder(), name.c_str(), autoValues.ptr(),
                               autoValues.length(), isColor);
-    wrapper->markDirty();
 }
 
 static void nativeUpdateIntUniforms(JNIEnv* env, MeshUniformBuilder* builder,
@@ -166,7 +164,6 @@
     ScopedUtfChars name(env, uniformName);
     const int values[4] = {value1, value2, value3, value4};
     nativeUpdateIntUniforms(env, wrapper->uniformBuilder(), name.c_str(), values, count);
-    wrapper->markDirty();
 }
 
 static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
@@ -176,7 +173,6 @@
     AutoJavaIntArray autoValues(env, values, 0);
     nativeUpdateIntUniforms(env, wrapper->uniformBuilder(), name.c_str(), autoValues.ptr(),
                             autoValues.length());
-    wrapper->markDirty();
 }
 
 static void MeshWrapper_destroy(Mesh* wrapper) {
diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h
index 6ccb212..40dfc9d 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.h
+++ b/libs/hwui/pipeline/skia/ShaderCache.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <FileBlobCache.h>
 #include <GrContextOptions.h>
 #include <SkRefCnt.h>
 #include <cutils/compiler.h>
@@ -32,7 +33,6 @@
 namespace android {
 
 class BlobCache;
-class FileBlobCache;
 
 namespace uirenderer {
 namespace skiapipeline {
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index 5c8285a..e0216b6 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -111,8 +111,8 @@
     }
 
     auto grContext = info.canvasContext.getGrContext();
-    for (auto mesh : mMeshes) {
-        mesh->updateSkMesh(grContext);
+    for (const auto& bufferData : mMeshBufferData) {
+        bufferData->updateBuffers(grContext);
     }
 
 #endif
@@ -181,7 +181,7 @@
 
     mDisplayList.reset();
 
-    mMeshes.clear();
+    mMeshBufferData.clear();
     mMutableImages.clear();
     mVectorDrawables.clear();
     mAnimatedImages.clear();
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index b9dc1c4..071a4e8 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <deque>
+#include <memory>
 
 #include "Mesh.h"
 #include "RecordingCanvas.h"
@@ -172,7 +173,7 @@
     std::deque<RenderNodeDrawable> mChildNodes;
     std::deque<FunctorDrawable*> mChildFunctors;
     std::vector<SkImage*> mMutableImages;
-    std::vector<const Mesh*> mMeshes;
+    std::vector<std::shared_ptr<const MeshBufferData>> mMeshBufferData;
 
 private:
     std::vector<Pair<VectorDrawableRoot*, SkMatrix>> mVectorDrawables;
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index e917f9a..45bfe1c 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -342,7 +342,7 @@
 }
 
 void SkiaRecordingCanvas::drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const Paint& paint) {
-    mDisplayList->mMeshes.push_back(&mesh);
+    mDisplayList->mMeshBufferData.push_back(mesh.refBufferData());
     mRecorder.drawMesh(mesh, blender, paint);
 }
 
diff --git a/media/OWNERS b/media/OWNERS
index 994a7b8..2e9276d 100644
--- a/media/OWNERS
+++ b/media/OWNERS
@@ -3,14 +3,9 @@
 elaurent@google.com
 essick@google.com
 etalvala@google.com
-hdmoon@google.com
 hunga@google.com
-insun@google.com
-jaewan@google.com
-jinpark@google.com
 jmtrivi@google.com
 jsharkey@android.com
-klhyun@google.com
 lajos@google.com
 nchalko@google.com
 philburk@google.com
@@ -20,8 +15,6 @@
 # go/android-fwk-media-solutions for info on areas of ownership.
 include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
 
-# SEO
-
 # SEA/KIR/BVE
 jtinker@google.com
 robertshih@google.com
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 4be282b..4217562 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -1690,6 +1690,18 @@
     }
 
     /**
+     * Query if the usage is a hidden (neither sdk nor SystemApi) usage
+     *
+     * @param usage the {@link android.media.AudioAttributes usage}
+     * @return {@code true} if the usage is {@link AudioAttributes#USAGE_VIRTUAL_SOURCE} or
+     *     {@code false} otherwise
+     * @hide
+     */
+    public static boolean isHiddenUsage(@AttributeUsage int usage) {
+        return usage == USAGE_VIRTUAL_SOURCE;
+    }
+
+    /**
      * Query if the content type is a valid sdk content type
      * @param contentType one of {@link AttributeContentType}
      * @return {@code true} if the content type is valid for sdk or {@code false} otherwise
diff --git a/media/java/android/media/FadeManagerConfiguration.java b/media/java/android/media/FadeManagerConfiguration.java
index e6ec2c3..eaafa59 100644
--- a/media/java/android/media/FadeManagerConfiguration.java
+++ b/media/java/android/media/FadeManagerConfiguration.java
@@ -694,7 +694,8 @@
     }
 
     private static boolean isUsageValid(int usage) {
-        return AudioAttributes.isSdkUsage(usage) || AudioAttributes.isSystemUsage(usage);
+        return AudioAttributes.isSdkUsage(usage) || AudioAttributes.isSystemUsage(usage)
+                || AudioAttributes.isHiddenUsage(usage);
     }
 
     private void ensureFadingIsEnabled() {
diff --git a/media/java/android/media/browse/OWNERS b/media/java/android/media/browse/OWNERS
deleted file mode 100644
index 916fc36..0000000
--- a/media/java/android/media/browse/OWNERS
+++ /dev/null
@@ -1,8 +0,0 @@
-# Bug component: 137631
-
-hdmoon@google.com
-insun@google.com
-jaewan@google.com
-jinpark@google.com
-klhyun@google.com
-gyumin@google.com
diff --git a/media/java/android/media/session/OWNERS b/media/java/android/media/session/OWNERS
deleted file mode 100644
index 916fc36..0000000
--- a/media/java/android/media/session/OWNERS
+++ /dev/null
@@ -1,8 +0,0 @@
-# Bug component: 137631
-
-hdmoon@google.com
-insun@google.com
-jaewan@google.com
-jinpark@google.com
-klhyun@google.com
-gyumin@google.com
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
index 236b1fd..c48a956 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
@@ -45,10 +45,8 @@
 @RunWith(AndroidJUnit4.class)
 @RequiresFlagsEnabled(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
 public final class FadeManagerConfigurationUnitTest {
-    private static final long DEFAULT_FADE_OUT_DURATION_MS =
-            FadeManagerConfiguration.getDefaultFadeOutDurationMillis();
-    private static final long DEFAULT_FADE_IN_DURATION_MS =
-            FadeManagerConfiguration.getDefaultFadeInDurationMillis();
+    private static final long DEFAULT_FADE_OUT_DURATION_MS = 2_000;
+    private static final long DEFAULT_FADE_IN_DURATION_MS = 1_000;
     private static final long TEST_FADE_OUT_DURATION_MS = 1_500;
     private static final long TEST_FADE_IN_DURATION_MS = 750;
     private static final int TEST_INVALID_USAGE = -10;
@@ -251,6 +249,20 @@
     }
 
     @Test
+    public void testGetDefaultFadeOutDuration() {
+        expect.withMessage("Default fade out duration")
+                .that(FadeManagerConfiguration.getDefaultFadeOutDurationMillis())
+                .isEqualTo(DEFAULT_FADE_OUT_DURATION_MS);
+    }
+
+    @Test
+    public void testGetDefaultFadeInDuration() {
+        expect.withMessage("Default fade in duration")
+                .that(FadeManagerConfiguration.getDefaultFadeInDurationMillis())
+                .isEqualTo(DEFAULT_FADE_IN_DURATION_MS);
+    }
+
+    @Test
     public void testSetFadeState_toDisable() {
         final int fadeState = FadeManagerConfiguration.FADE_STATE_DISABLED;
         FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
@@ -746,6 +758,87 @@
                 .isEqualTo(FadeManagerConfiguration.CREATOR.createFromParcel(parcel));
     }
 
+    @Test
+    public void testGetFadeOutVolumeShaperConfigForUsage_withInvalidUsage_fails() {
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+                mFmc.getFadeOutVolumeShaperConfigForUsage(TEST_INVALID_USAGE)
+        );
+
+        expect.withMessage("Fade out volume shaper config for invalid usage exception")
+                .that(thrown).hasMessageThat().contains("Invalid usage");
+    }
+
+    @Test
+    public void testGetFadeInVolumeShaperConfigForUsage_withInvalidUsage_fails() {
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+                mFmc.getFadeInVolumeShaperConfigForUsage(TEST_INVALID_USAGE)
+        );
+
+        expect.withMessage("Fade in volume shaper config for invalid usage exception")
+                .that(thrown).hasMessageThat().contains("Invalid usage");
+    }
+
+    @Test
+    public void testGetFadeVolumeShaperConfigForUsage_forSdkUsages() {
+        FadeManagerConfiguration.Builder builder = new FadeManagerConfiguration.Builder();
+        for (int usage : AudioAttributes.getSdkUsages()) {
+            builder.addFadeableUsage(usage);
+            builder.setFadeOutVolumeShaperConfigForUsage(usage, TEST_FADE_OUT_VOLUME_SHAPER_CONFIG);
+            builder.setFadeInVolumeShaperConfigForUsage(usage, TEST_FADE_IN_VOLUME_SHAPER_CONFIG);
+        }
+        FadeManagerConfiguration fmc = builder.build();
+
+        for (int usage : AudioAttributes.getSdkUsages()) {
+            expect.withMessage("Fade out volume shaper config for sdk usage")
+                    .that(fmc.getFadeOutVolumeShaperConfigForUsage(usage))
+                    .isEqualTo(TEST_FADE_OUT_VOLUME_SHAPER_CONFIG);
+            expect.withMessage("Fade in volume shaper config for sdk usage")
+                    .that(fmc.getFadeInVolumeShaperConfigForUsage(usage))
+                    .isEqualTo(TEST_FADE_IN_VOLUME_SHAPER_CONFIG);
+        }
+    }
+
+    @Test
+    public void testGetFadeVolumeShaperConfigForUsage_forSystemUsages() {
+        int[] systemUsages = {AudioAttributes.USAGE_CALL_ASSISTANT, AudioAttributes.USAGE_EMERGENCY,
+                AudioAttributes.USAGE_SAFETY, AudioAttributes.USAGE_VEHICLE_STATUS,
+                AudioAttributes.USAGE_ANNOUNCEMENT};
+        FadeManagerConfiguration.Builder builder = new FadeManagerConfiguration.Builder();
+        for (int usage : systemUsages) {
+            builder.addFadeableUsage(usage);
+            builder.setFadeOutVolumeShaperConfigForUsage(usage, TEST_FADE_OUT_VOLUME_SHAPER_CONFIG);
+            builder.setFadeInVolumeShaperConfigForUsage(usage, TEST_FADE_IN_VOLUME_SHAPER_CONFIG);
+        }
+        FadeManagerConfiguration fmc = builder.build();
+
+        for (int usage : systemUsages) {
+            expect.withMessage("Fade out volume shaper config for system usage")
+                    .that(fmc.getFadeOutVolumeShaperConfigForUsage(usage))
+                    .isEqualTo(TEST_FADE_OUT_VOLUME_SHAPER_CONFIG);
+            expect.withMessage("Fade in volume shaper config for system usage")
+                    .that(fmc.getFadeInVolumeShaperConfigForUsage(usage))
+                    .isEqualTo(TEST_FADE_IN_VOLUME_SHAPER_CONFIG);
+        }
+    }
+
+    @Test
+    public void testGetFadeVolumeShaperConfigForUsage_forHiddenUsage() {
+        int hiddenUsage = AudioAttributes.USAGE_VIRTUAL_SOURCE;
+        FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
+                .addFadeableUsage(hiddenUsage)
+                .setFadeOutVolumeShaperConfigForUsage(hiddenUsage,
+                        TEST_FADE_OUT_VOLUME_SHAPER_CONFIG)
+                .setFadeInVolumeShaperConfigForUsage(hiddenUsage,
+                        TEST_FADE_IN_VOLUME_SHAPER_CONFIG).build();
+
+        expect.withMessage("Fade out volume shaper config for hidden usage")
+                .that(fmc.getFadeOutVolumeShaperConfigForUsage(hiddenUsage))
+                .isEqualTo(TEST_FADE_OUT_VOLUME_SHAPER_CONFIG);
+        expect.withMessage("Fade in volume shaper config for hidden usage")
+                .that(fmc.getFadeInVolumeShaperConfigForUsage(hiddenUsage))
+                .isEqualTo(TEST_FADE_IN_VOLUME_SHAPER_CONFIG);
+    }
+
     private static AudioAttributes createAudioAttributesForUsage(int usage) {
         if (AudioAttributes.isSystemUsage(usage)) {
             return new AudioAttributes.Builder().setSystemUsage(usage).build();
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 752ebdf..4812685 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -58,6 +58,7 @@
         "configuration.cpp",
         "hardware_buffer_jni.cpp",
         "input.cpp",
+        "input_transfer_token.cpp",
         "looper.cpp",
         "native_activity.cpp",
         "native_window_jni.cpp",
diff --git a/native/android/input_transfer_token.cpp b/native/android/input_transfer_token.cpp
new file mode 100644
index 0000000..501e1d3
--- /dev/null
+++ b/native/android/input_transfer_token.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "InputTransferToken"
+
+#include <android/input_transfer_token_jni.h>
+#include <android_runtime/android_window_InputTransferToken.h>
+#include <gui/InputTransferToken.h>
+#include <log/log_main.h>
+
+using namespace android;
+
+#define CHECK_NOT_NULL(name) \
+    LOG_ALWAYS_FATAL_IF(name == nullptr, "nullptr passed as " #name " argument");
+
+void InputTransferToken_acquire(InputTransferToken* inputTransferToken) {
+    // incStrong/decStrong token must be the same, doesn't matter what it is
+    inputTransferToken->incStrong((void*)InputTransferToken_acquire);
+}
+
+void InputTransferToken_release(InputTransferToken* inputTransferToken) {
+    // incStrong/decStrong token must be the same, doesn't matter what it is
+    inputTransferToken->decStrong((void*)InputTransferToken_acquire);
+}
+
+AInputTransferToken* AInputTransferToken_fromJava(JNIEnv* env, jobject inputTransferTokenObj) {
+    CHECK_NOT_NULL(env);
+    CHECK_NOT_NULL(inputTransferTokenObj);
+    InputTransferToken* inputTransferToken =
+            android_window_InputTransferToken_getNativeInputTransferToken(env,
+                                                                          inputTransferTokenObj);
+    CHECK_NOT_NULL(inputTransferToken);
+    InputTransferToken_acquire(inputTransferToken);
+    return reinterpret_cast<AInputTransferToken*>(inputTransferToken);
+}
+
+jobject AInputTransferToken_toJava(JNIEnv* _Nonnull env,
+                                   const AInputTransferToken* aInputTransferToken) {
+    CHECK_NOT_NULL(env);
+    CHECK_NOT_NULL(aInputTransferToken);
+    const InputTransferToken* inputTransferToken =
+            reinterpret_cast<const InputTransferToken*>(aInputTransferToken);
+    return android_window_InputTransferToken_getJavaInputTransferToken(env, inputTransferToken);
+}
+
+void AInputTransferToken_release(AInputTransferToken* aInputTransferToken) {
+    CHECK_NOT_NULL(aInputTransferToken);
+    InputTransferToken* inputTransferToken =
+            reinterpret_cast<InputTransferToken*>(aInputTransferToken);
+    InputTransferToken_release(inputTransferToken);
+}
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 35e37b2..b2925bf 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -98,6 +98,9 @@
     AInputQueue_getEvent;
     AInputQueue_hasEvents;
     AInputQueue_preDispatchEvent;
+    AInputTransferToken_fromJava; # introduced=35
+    AInputTransferToken_release; # introduced=35
+    AInputTransferToken_toJava; # introduced=35
     AKeyEvent_getAction;
     AKeyEvent_getDownTime;
     AKeyEvent_getEventTime;
diff --git a/nfc/java/android/nfc/cardemulation/PollingFrame.java b/nfc/java/android/nfc/cardemulation/PollingFrame.java
index 7028c8f..af63a6e 100644
--- a/nfc/java/android/nfc/cardemulation/PollingFrame.java
+++ b/nfc/java/android/nfc/cardemulation/PollingFrame.java
@@ -174,6 +174,16 @@
                 && frame.getBoolean(KEY_POLLING_LOOP_TRIGGERED_AUTOTRANSACT);
     }
 
+    /**
+     * Constructor for Polling Frames.
+     *
+     * @param type the type of the frame
+     * @param data a byte array of the data contained in the frame
+     * @param gain the vendor-specific gain of the field
+     * @param timestamp the timestamp in millisecones
+     * @param triggeredAutoTransact whether or not this frame triggered the device to start a
+     * transaction automatically
+     */
     public PollingFrame(@PollingFrameType int type, @Nullable byte[] data,
             int gain, int timestamp, boolean triggeredAutoTransact) {
         mType = type;
diff --git a/packages/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/RescueParty.java b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
index 0fcec26..d0fee44 100644
--- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
@@ -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/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/IllustrationPreference/res/layout/illustration_preference.xml b/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml
index 685e259..0ae9c26 100644
--- a/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml
+++ b/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml
@@ -19,7 +19,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_height="wrap_content"
     android:layout_width="match_parent"
-    android:importantForAccessibility="noHideDescendants"
+    android:importantForAccessibility="no"
     android:gravity="center"
     android:orientation="horizontal">
 
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
index f4d4dba..815a101 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
@@ -24,6 +24,7 @@
 import android.graphics.drawable.AnimationDrawable;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
@@ -33,16 +34,18 @@
 import android.widget.ImageView;
 
 import androidx.annotation.RawRes;
+import androidx.annotation.StringRes;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceViewHolder;
 import androidx.vectordrawable.graphics.drawable.Animatable2Compat;
 
+import com.android.settingslib.widget.preference.illustration.R;
+
 import com.airbnb.lottie.LottieAnimationView;
 import com.airbnb.lottie.LottieDrawable;
 
 import java.io.FileNotFoundException;
 import java.io.InputStream;
-import com.android.settingslib.widget.preference.illustration.R;
 
 /**
  * IllustrationPreference is a preference that can play lottie format animation
@@ -62,8 +65,8 @@
     private Drawable mImageDrawable;
     private View mMiddleGroundView;
     private OnBindListener mOnBindListener;
-
     private boolean mLottieDynamicColor;
+    private CharSequence mContentDescription;
 
     /**
      * Interface to listen in on when {@link #onBindViewHolder(PreferenceViewHolder)} occurs.
@@ -123,7 +126,10 @@
                 (FrameLayout) holder.findViewById(R.id.middleground_layout);
         final LottieAnimationView illustrationView =
                 (LottieAnimationView) holder.findViewById(R.id.lottie_view);
-
+        if (illustrationView != null && !TextUtils.isEmpty(mContentDescription)) {
+            illustrationView.setContentDescription(mContentDescription);
+            illustrationView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+        }
         // To solve the problem of non-compliant illustrations, we set the frame height
         // to 300dp and set the length of the short side of the screen to
         // the width of the frame.
@@ -208,6 +214,29 @@
     }
 
     /**
+     * To set content description of the {@link Illustration Preference}. This can use for talkback
+     * environment if developer wants to have a customization content.
+     *
+     * @param contentDescription The CharSequence of the content description.
+     */
+    public void setContentDescription(CharSequence contentDescription) {
+        if (!TextUtils.equals(mContentDescription, contentDescription)) {
+            mContentDescription = contentDescription;
+            notifyChanged();
+        }
+    }
+
+    /**
+     * To set content description of the {@link Illustration Preference}. This can use for talkback
+     * environment if developer wants to have a customization content.
+     *
+     * @param contentDescriptionResId The resource id of the content description.
+     */
+    public void setContentDescription(@StringRes int contentDescriptionResId) {
+        setContentDescription(getContext().getText(contentDescriptionResId));
+    }
+
+    /**
      * Gets the lottie illustration resource id.
      */
     public int getLottieAnimationResId() {
diff --git a/packages/SettingsLib/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/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
index 79c5ebb..5dd7caf 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
@@ -112,7 +112,8 @@
                     isVisible = { isVisible0 },
                     onDismiss = { isVisible0 = false },
                     buttons = listOf(
-                        CardButton(text = "Action") {},
+                        CardButton(text = "Override") {},
+                        CardButton(text = "Learn more") {},
                     ),
                 ),
                 CardModel(
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png
index 63efaf5..3e016f7 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png
index bb518c0..d156f95 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png
index ae11f81..b8bb25f 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png
index bb518c0..d156f95 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_actionButtons.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_actionButtons.png
index a3692cd..63983ee 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_actionButtons.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_actionButtons.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_barChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_barChart.png
index 233d088..8fcc350 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_barChart.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_barChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png
index 10869f2..c2f6165 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_imageIllustration.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_imageIllustration.png
index 3eaecc1..f32d7421 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_imageIllustration.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_imageIllustration.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_lineChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_lineChart.png
index ca61911..6659d7c 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_lineChart.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_lineChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png
index 8333e68..36cbadc 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_pieChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_pieChart.png
index 19d0afd..7b6e702 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_pieChart.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_pieChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_preference.png
index fcfd1d8..cd44fb8 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_preference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png
index 693c592..19c028e 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png
index 0521368..8a2b800 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png
index 5bb318f..a279481 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_switchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_switchPreference.png
index d0d014e..9aee004 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_switchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_switchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_twoTargetSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_twoTargetSwitchPreference.png
index 5bd1144..cc74aac 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_twoTargetSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_twoTargetSwitchPreference.png
Binary files differ
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..f30a957 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,25 +44,21 @@
     private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
     private val screenshotRule =
         ScreenshotTestRule(
-            SettingsGoldenImagePathManager(
+            SettingsGoldenPathManager(
                 getEmulatedDevicePathConfig(emulationSpec),
                 assetsPathRelativeToBuildRoot
             )
         )
     private val composeRule = createAndroidComposeRule<ComponentActivity>()
-    private val roboRule =
-        RuleChain.outerRule(deviceEmulationRule)
-            .around(screenshotRule)
-            .around(composeRule)
     private val delegateRule =
         RuleChain.outerRule(colorsRule)
-            .around(roboRule)
+            .around(deviceEmulationRule)
+            .around(screenshotRule)
+            .around(composeRule)
     private val matcher = UnitTestBitmapMatcher
-    private val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false
 
     override fun apply(base: Statement, description: Description): Statement {
-        val ruleToApply = if (isRobolectric) roboRule else delegateRule
-        return ruleToApply.apply(base, description)
+        return delegateRule.apply(base, description)
     }
 
     /**
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
index 621825a..f5cbe8f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
@@ -21,6 +21,8 @@
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
+import androidx.compose.foundation.layout.FlowRow
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxWidth
@@ -165,10 +167,11 @@
     }
 }
 
+@OptIn(ExperimentalLayoutApi::class)
 @Composable
 private fun Buttons(buttons: List<CardButton>, color: Color) {
     if (buttons.isNotEmpty()) {
-        Row(
+        FlowRow(
             modifier = Modifier.fillMaxWidth(),
             horizontalArrangement = Arrangement.spacedBy(
                 space = SettingsDimension.itemPaddingEnd,
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/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index a4bc235..8a5dfef 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1485,7 +1485,7 @@
     <string name="user_nickname">Nickname</string>
 
     <!-- Confirmation message on dialog for editing user name and profile picture. Inform user on who will be able to see the changes [CHAR LIMIT=NONE]-->
-    <string name="edit_user_info_message">Your name and picture will be visible to anyone that uses this device.</string>
+    <string name="edit_user_info_message">The name and picture you choose will be visible to anyone who uses this device.</string>
 
     <!-- Label for adding a new user in the user switcher [CHAR LIMIT=35] -->
     <string name="user_add_user">Add user</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index c2c82b3..b8624fd 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -1029,15 +1029,17 @@
 
     @Override
     public String toString() {
-        return "CachedBluetoothDevice{"
-                + "anonymizedAddress="
-                + mDevice.getAnonymizedAddress()
-                + ", name="
-                + getName()
-                + ", groupId="
-                + mGroupId
-                + ", member=" + mMemberDevices
-                + "}";
+        StringBuilder builder = new StringBuilder("CachedBluetoothDevice{");
+        builder.append("anonymizedAddress=").append(mDevice.getAnonymizedAddress());
+        builder.append(", name=").append(getName());
+        builder.append(", groupId=").append(mGroupId);
+        builder.append(", member=").append(mMemberDevices);
+        if (isHearingAidDevice()) {
+            builder.append(", hearingAidInfo=").append(mHearingAidInfo);
+            builder.append(", subDevice=").append(mSubDevice);
+        }
+        builder.append("}");
+        return builder.toString();
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index bd27c89..1118efc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -81,11 +81,13 @@
     public static final int BROADCAST_STATE_UNKNOWN = 0;
     public static final int BROADCAST_STATE_ON = 1;
     public static final int BROADCAST_STATE_OFF = 2;
+
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(
             prefix = {"BROADCAST_STATE_"},
             value = {BROADCAST_STATE_UNKNOWN, BROADCAST_STATE_ON, BROADCAST_STATE_OFF})
     public @interface BroadcastState {}
+
     private static final String SETTINGS_PKG = "com.android.settings";
     private static final String TAG = "LocalBluetoothLeBroadcast";
     private static final boolean DEBUG = BluetoothUtils.D;
@@ -1068,7 +1070,7 @@
             return;
         }
         int fallbackActiveGroupId = getFallbackActiveGroupId();
-        if (targetCachedDevice.getGroupId() == fallbackActiveGroupId) {
+        if (getGroupId(targetCachedDevice) == fallbackActiveGroupId) {
             Log.d(
                     TAG,
                     "Skip updateFallbackActiveDeviceIfNeeded, already is fallback: "
@@ -1091,6 +1093,23 @@
                 BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
     }
 
+    private int getGroupId(CachedBluetoothDevice cachedDevice) {
+        int groupId = cachedDevice.getGroupId();
+        String anonymizedAddress = cachedDevice.getDevice().getAnonymizedAddress();
+        if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
+            Log.d(TAG, "getGroupId by CSIP profile for device: " + anonymizedAddress);
+            return groupId;
+        }
+        for (LocalBluetoothProfile profile : cachedDevice.getProfiles()) {
+            if (profile instanceof LeAudioProfile) {
+                Log.d(TAG, "getGroupId by LEA profile for device: " + anonymizedAddress);
+                return ((LeAudioProfile) profile).getGroupId(cachedDevice.getDevice());
+            }
+        }
+        Log.d(TAG, "getGroupId return invalid id for device: " + anonymizedAddress);
+        return BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
+    }
+
     private void notifyBroadcastStateChange(@BroadcastState int state) {
         if (!mContext.getPackageName().equals(SETTINGS_PKG)) {
             Log.d(TAG, "Skip notifyBroadcastStateChange, not triggered by Settings.");
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index 0df4615..21cc9a8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -161,11 +161,11 @@
 
     override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) =
         withContext(backgroundCoroutineContext) {
-            if (isMuted) {
-                audioManager.adjustStreamVolume(audioStream.value, 0, AudioManager.ADJUST_MUTE)
-            } else {
-                audioManager.adjustStreamVolume(audioStream.value, 0, AudioManager.ADJUST_UNMUTE)
-            }
+            audioManager.adjustStreamVolume(
+                audioStream.value,
+                if (isMuted) AudioManager.ADJUST_MUTE else AudioManager.ADJUST_UNMUTE,
+                0,
+            )
         }
 
     private fun getMinVolume(stream: AudioStream): Int =
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
index 1586b8f..c9ac97d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
@@ -84,10 +84,10 @@
                     (audioStreamModel.audioStream.value == AudioManager.STREAM_NOTIFICATION &&
                         audioStreamModel.isMuted)
             ) {
-                return 0
+                return audioStreamModel.minVolume
             }
         } else if (audioStreamModel.isMuted) {
-            return 0
+            return audioStreamModel.minVolume
         }
         return audioStreamModel.volume
     }
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
index 1728a80..9860cd8 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
@@ -77,13 +77,13 @@
         `when`(audioManager.getStreamMaxVolume(anyInt())).thenReturn(MAX_VOLUME)
         `when`(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL)
         `when`(audioManager.setStreamVolume(anyInt(), anyInt(), anyInt())).then {
-            val streamType = it.arguments[1] as Int
-            volumeByStream[it.arguments[0] as Int] = streamType
+            val streamType = it.arguments[0] as Int
+            volumeByStream[it.arguments[0] as Int] = it.arguments[1] as Int
             triggerEvent(AudioManagerEvent.StreamVolumeChanged(AudioStream(streamType)))
         }
         `when`(audioManager.adjustStreamVolume(anyInt(), anyInt(), anyInt())).then {
             val streamType = it.arguments[0] as Int
-            isMuteByStream[streamType] = it.arguments[2] == AudioManager.ADJUST_MUTE
+            isMuteByStream[streamType] = it.arguments[1] == AudioManager.ADJUST_MUTE
             triggerEvent(AudioManagerEvent.StreamMuteChanged(AudioStream(streamType)))
         }
         `when`(audioManager.getStreamVolume(anyInt())).thenAnswer {
diff --git a/packages/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/res/values/strings.xml b/packages/SettingsProvider/res/values/strings.xml
index 76bea31..9ca575e 100644
--- a/packages/SettingsProvider/res/values/strings.xml
+++ b/packages/SettingsProvider/res/values/strings.xml
@@ -19,14 +19,4 @@
 <resources>
     <!-- Name of the activity for Settings storage. -->
     <string name="app_label">Settings Storage</string>
-
-    <!-- A notification is shown when the user's softap config has been changed due to underlying
-     hardware restrictions. This is the notifications's title.
-     [CHAR_LIMIT=NONE] -->
-    <string name="wifi_softap_config_change">Hotspot settings have changed</string>
-
-    <!-- A notification is shown when the user's softap config has been changed due to underlying
-         hardware restrictions. This is the notification's summary message.
-         [CHAR_LIMIT=NONE] -->
-    <string name="wifi_softap_config_change_summary">Tap to see details</string>
 </resources>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 7b49608..e5d62f8 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -1133,16 +1133,15 @@
             // Depending on device hardware, we may need to notify the user of a setting change
             SoftApConfiguration storedConfig = mWifiManager.getSoftApConfiguration();
 
-            if (isNeedToNotifyUserConfigurationHasChanged(configInCloud, storedConfig)) {
-                Log.d(TAG, "restored ap configuration requires a conversion, notify the user"
+            if (isConfigurationHasChanged(configInCloud, storedConfig)) {
+                Log.d(TAG, "restored ap configuration requires a conversion: "
                         + ", configInCloud is " + configInCloud + " but storedConfig is "
                         + storedConfig);
-                WifiSoftApConfigChangedNotifier.notifyUserOfConfigConversion(this);
             }
         }
     }
 
-    private boolean isNeedToNotifyUserConfigurationHasChanged(SoftApConfiguration configInCloud,
+    private boolean isConfigurationHasChanged(SoftApConfiguration configInCloud,
             SoftApConfiguration storedConfig) {
         // Check if the cloud configuration was modified when restored to the device.
         // All elements of the configuration are compared except:
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 3266c12..b151a53 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -60,10 +60,12 @@
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
+import java.io.BufferedWriter;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
+import java.io.FileWriter;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.nio.file.Files;
@@ -161,6 +163,11 @@
     private static final String APEX_DIR = "/apex";
     private static final String APEX_ACONFIG_PATH_SUFFIX = "/etc/aconfig_flags.pb";
 
+    private static final String STORAGE_MIGRATION_FLAG =
+            "core_experiments_team_internal/com.android.providers.settings.storage_test_mission_1";
+    private static final String STORAGE_MIGRATION_LOG =
+            "/metadata/aconfig/flags/storage_migration.log";
+
     /**
      * This tag is applied to all aconfig default value-loaded flags.
      */
@@ -1439,6 +1446,20 @@
                     }
                 }
 
+                if (name != null && name.equals(STORAGE_MIGRATION_FLAG) && value.equals("true")) {
+                    File file = new File(STORAGE_MIGRATION_LOG);
+                    if (!file.exists()) {
+                        try (BufferedWriter writer =
+                                new BufferedWriter(new FileWriter(STORAGE_MIGRATION_LOG))) {
+                            final long timestamp = System.currentTimeMillis();
+                            String entry = String.format("%d | Log init", timestamp);
+                            writer.write(entry);
+                        } catch (IOException e) {
+                            Slog.e(LOG_TAG, "failed to write storage migration file", e);
+                        }
+                    }
+                }
+
                 mSettings.put(name, new Setting(name, value, defaultValue, packageName, tag,
                         fromSystem, id, isPreservedInRestore));
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java b/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java
deleted file mode 100644
index dc51c40..0000000
--- a/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.providers.settings;
-
-import android.app.ActivityManager;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.os.UserHandle;
-import android.provider.Settings;
-
-import com.android.internal.messages.nano.SystemMessageProto;
-import com.android.internal.notification.SystemNotificationChannels;
-
-import java.util.List;
-
-/**
- * Helper class for sending notifications when the user's Soft AP config was changed upon restore.
- */
-public class WifiSoftApConfigChangedNotifier {
-    private WifiSoftApConfigChangedNotifier() {}
-
-    /**
-     * Send a notification informing the user that their' Soft AP Config was changed upon restore.
-     * When the user taps on the notification, they are taken to the Wifi Tethering page in
-     * Settings.
-     */
-    public static void notifyUserOfConfigConversion(Context context) {
-        NotificationManager notificationManager =
-                context.getSystemService(NotificationManager.class);
-
-        // create channel, or update it if it already exists
-        NotificationChannel channel = new NotificationChannel(
-                SystemNotificationChannels.NETWORK_STATUS,
-                context.getString(
-                        com.android.internal.R.string.notification_channel_network_status),
-                NotificationManager.IMPORTANCE_LOW);
-        notificationManager.createNotificationChannel(channel);
-
-        notificationManager.notify(
-                SystemMessageProto.SystemMessage.NOTE_SOFTAP_CONFIG_CHANGED,
-                createConversionNotification(context));
-    }
-
-    private static Notification createConversionNotification(Context context) {
-        Resources resources = context.getResources();
-        CharSequence title = resources.getText(R.string.wifi_softap_config_change);
-        CharSequence contentSummary = resources.getText(R.string.wifi_softap_config_change_summary);
-        int color = resources.getColor(
-                android.R.color.system_notification_accent_color, context.getTheme());
-
-        return new Notification.Builder(context, SystemNotificationChannels.NETWORK_STATUS)
-                .setSmallIcon(R.drawable.ic_wifi_settings)
-                .setPriority(Notification.PRIORITY_HIGH)
-                .setCategory(Notification.CATEGORY_SYSTEM)
-                .setContentTitle(title)
-                .setContentText(contentSummary)
-                .setContentIntent(getPendingActivity(context))
-                .setTicker(title)
-                .setShowWhen(false)
-                .setLocalOnly(true)
-                .setColor(color)
-                .setStyle(new Notification.BigTextStyle()
-                        .setBigContentTitle(title)
-                        .setSummaryText(contentSummary))
-                .setAutoCancel(true)
-                .build();
-    }
-
-    private static PendingIntent getPendingActivity(Context context) {
-        Intent intent = new Intent("com.android.settings.WIFI_TETHER_SETTINGS")
-                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-                .setPackage(getSettingsPackageName(context));
-        return PendingIntent.getActivity(context, 0, intent,
-                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
-    }
-
-    /**
-     * @return Get settings package name.
-     */
-    private static String getSettingsPackageName(Context context) {
-        if (context == null) return null;
-
-        Intent intent = new Intent(Settings.ACTION_WIFI_SETTINGS);
-        List<ResolveInfo> resolveInfos = context.getPackageManager().queryIntentActivitiesAsUser(
-                intent, PackageManager.MATCH_SYSTEM_ONLY | PackageManager.MATCH_DEFAULT_ONLY,
-                UserHandle.of(ActivityManager.getCurrentUser()));
-        if (resolveInfos == null || resolveInfos.isEmpty()) {
-            return "com.android.settings";
-        }
-        return resolveInfos.get(0).activityInfo.packageName;
-    }
-}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
index 2e14e9b..c572bdb 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
+++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
@@ -33,3 +33,10 @@
     bug: "327383546"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "storage_test_mission_1"
+    namespace: "core_experiments_team_internal"
+    description: "If this flag is detected as true on boot, writes a logfile to track storage migration correctness."
+    bug: "328444881"
+}
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 432efb1..d9c371a 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" />
@@ -433,7 +434,7 @@
             </intent-filter>
         </receiver>
 
-        <activity android:name=".screenshot.LongScreenshotActivity"
+        <activity android:name=".screenshot.scroll.LongScreenshotActivity"
                   android:theme="@style/LongScreenshotActivity"
                   android:process=":screenshot"
                   android:exported="false"
@@ -901,7 +902,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 85bdb29..e5e3469 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"
@@ -628,3 +635,11 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "qs_ui_refactor"
+    namespace: "systemui"
+    description: "Enables the new QS UI pipeline that follows recommended architecture and uses"
+      " Compose for the UI."
+    bug: "325099249"
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt b/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
index 9a99649..f779cf36 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
@@ -62,6 +62,7 @@
 import androidx.compose.ui.layout.layoutId
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.res.colorResource
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.IntOffset
@@ -69,7 +70,6 @@
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.util.fastFirst
 import androidx.compose.ui.util.fastFirstOrNull
-import com.android.compose.theme.LocalAndroidColorScheme
 
 /**
  * Platform slider implementation that displays a slider with an [icon] and a [label] at the start.
@@ -143,12 +143,14 @@
             thumb = { Spacer(Modifier.size(thumbSize)) },
         )
 
-        Spacer(
-            Modifier.padding(8.dp)
-                .size(4.dp)
-                .align(Alignment.CenterEnd)
-                .background(color = colors.indicatorColor, shape = CircleShape)
-        )
+        if (enabled) {
+            Spacer(
+                Modifier.padding(8.dp)
+                    .size(4.dp)
+                    .align(Alignment.CenterEnd)
+                    .background(color = colors.indicatorColor, shape = CircleShape)
+            )
+        }
     }
 }
 
@@ -219,9 +221,9 @@
                     )
                 Box(
                     modifier =
-                        Modifier.layoutId(TrackComponent.Label).offset {
-                            IntOffset(offsetX.toInt(), 0)
-                        },
+                        Modifier.layoutId(TrackComponent.Label)
+                            .offset { IntOffset(offsetX.toInt(), 0) }
+                            .padding(end = 16.dp),
                     contentAlignment = Alignment.CenterStart,
                 ) {
                     CompositionLocalProvider(
@@ -452,11 +454,11 @@
 @Composable
 private fun lightThemePlatformSliderColors() =
     PlatformSliderColors(
-        trackColor = LocalAndroidColorScheme.current.tertiaryFixedDim,
+        trackColor = colorResource(android.R.color.system_accent3_200),
         indicatorColor = MaterialTheme.colorScheme.tertiary,
         iconColor = MaterialTheme.colorScheme.onTertiary,
         labelColorOnIndicator = MaterialTheme.colorScheme.onTertiary,
-        labelColorOnTrack = LocalAndroidColorScheme.current.onTertiaryFixed,
+        labelColorOnTrack = MaterialTheme.colorScheme.onTertiaryContainer,
         disabledTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest,
         disabledIndicatorColor = MaterialTheme.colorScheme.surfaceContainerHighest,
         disabledIconColor = MaterialTheme.colorScheme.outline,
@@ -467,11 +469,11 @@
 @Composable
 private fun darkThemePlatformSliderColors() =
     PlatformSliderColors(
-        trackColor = MaterialTheme.colorScheme.tertiary,
+        trackColor = colorResource(android.R.color.system_accent3_600),
         indicatorColor = MaterialTheme.colorScheme.tertiary,
-        iconColor = MaterialTheme.colorScheme.onTertiaryContainer,
+        iconColor = MaterialTheme.colorScheme.onTertiary,
         labelColorOnIndicator = MaterialTheme.colorScheme.onTertiary,
-        labelColorOnTrack = LocalAndroidColorScheme.current.onTertiaryFixed,
+        labelColorOnTrack = colorResource(android.R.color.system_accent3_900),
         disabledTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest,
         disabledIndicatorColor = MaterialTheme.colorScheme.surfaceContainerHighest,
         disabledIconColor = MaterialTheme.colorScheme.outline,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 0d6b710..6a510bd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -85,6 +85,8 @@
 import androidx.compose.ui.graphics.ColorFilter
 import androidx.compose.ui.graphics.ColorMatrix
 import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.key.onPreviewKeyEvent
+import androidx.compose.ui.input.pointer.motionEventSpy
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.layout.boundsInWindow
@@ -177,33 +179,43 @@
                 }
                 .thenIf(!viewModel.isEditMode) {
                     Modifier.pointerInput(
-                        gridState,
-                        contentOffset,
-                        communalContent,
-                        gridCoordinates
-                    ) {
-                        detectLongPressGesture { offset ->
-                            // Deduct both grid offset relative to its container and content offset.
-                            val adjustedOffset =
-                                gridCoordinates?.let {
-                                    offset - it.positionInWindow() - contentOffset
+                            gridState,
+                            contentOffset,
+                            communalContent,
+                            gridCoordinates
+                        ) {
+                            detectLongPressGesture { offset ->
+                                // Deduct both grid offset relative to its container and content
+                                // offset.
+                                val adjustedOffset =
+                                    gridCoordinates?.let {
+                                        offset - it.positionInWindow() - contentOffset
+                                    }
+                                val index =
+                                    adjustedOffset?.let { firstIndexAtOffset(gridState, it) }
+                                // Display the button only when the gesture initiates from widgets,
+                                // the CTA tile, or an empty area on the screen. UMO/smartspace have
+                                // their own long-press handlers. To prevent user confusion, we
+                                // should
+                                // not display this button.
+                                if (
+                                    index == null ||
+                                        communalContent[index].isWidgetContent() ||
+                                        communalContent[index] is
+                                            CommunalContentModel.CtaTileInViewMode
+                                ) {
+                                    isButtonToEditWidgetsShowing = true
                                 }
-                            val index = adjustedOffset?.let { firstIndexAtOffset(gridState, it) }
-                            // Display the button only when the gesture initiates from widgets,
-                            // the CTA tile, or an empty area on the screen. UMO/smartspace have
-                            // their own long-press handlers. To prevent user confusion, we should
-                            // not display this button.
-                            if (
-                                index == null ||
-                                    communalContent[index].isWidgetContent() ||
-                                    communalContent[index] is CommunalContentModel.CtaTileInViewMode
-                            ) {
-                                isButtonToEditWidgetsShowing = true
+                                val key =
+                                    index?.let { keyAtIndexIfEditable(communalContent, index) }
+                                viewModel.setSelectedKey(key)
                             }
-                            val key = index?.let { keyAtIndexIfEditable(communalContent, index) }
-                            viewModel.setSelectedKey(key)
                         }
-                    }
+                        .onPreviewKeyEvent {
+                            onKeyEvent(viewModel)
+                            false
+                        }
+                        .motionEventSpy { onMotionEvent(viewModel) }
                 },
     ) {
         CommunalHubLazyGrid(
@@ -311,6 +323,14 @@
     }
 }
 
+private fun onKeyEvent(viewModel: BaseCommunalViewModel) {
+    viewModel.signalUserInteraction()
+}
+
+private fun onMotionEvent(viewModel: BaseCommunalViewModel) {
+    viewModel.signalUserInteraction()
+}
+
 @Composable
 private fun ScrollOnNewSmartspaceEffect(
     viewModel: BaseCommunalViewModel,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
index 5d9b014..eedff89 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
@@ -66,6 +66,7 @@
 import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.compose.animation.Expandable
+import com.android.compose.animation.scene.SceneScope
 import com.android.compose.modifiers.background
 import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.compose.theme.colorAttr
@@ -77,16 +78,16 @@
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.qs.ui.composable.QuickSettings
 import com.android.systemui.qs.ui.composable.QuickSettingsTheme
 import com.android.systemui.res.R
 import kotlinx.coroutines.launch
 
 @Composable
-fun FooterActionsWithAnimatedVisibility(
+fun SceneScope.FooterActionsWithAnimatedVisibility(
     viewModel: FooterActionsViewModel,
     isCustomizing: Boolean,
     lifecycleOwner: LifecycleOwner,
-    footerActionsModifier: (Modifier) -> Modifier,
     modifier: Modifier = Modifier,
 ) {
     AnimatedVisibility(visible = !isCustomizing, modifier = modifier.fillMaxWidth()) {
@@ -96,7 +97,7 @@
             FooterActions(
                 viewModel = viewModel,
                 qsVisibilityLifecycleOwner = lifecycleOwner,
-                modifier = footerActionsModifier(Modifier),
+                modifier = Modifier.element(QuickSettings.Elements.FooterActions),
             )
         }
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 6ae1410..5b9213a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -60,7 +60,6 @@
 import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.qs.footer.ui.compose.FooterActions
 import com.android.systemui.qs.footer.ui.compose.FooterActionsWithAnimatedVisibility
 import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
 import com.android.systemui.res.R
@@ -249,9 +248,6 @@
                 viewModel = footerActionsViewModel,
                 isCustomizing = isCustomizing,
                 lifecycleOwner = lifecycleOwner,
-                footerActionsModifier = { modifier ->
-                    modifier.element(QuickSettings.Elements.FooterActions)
-                },
                 modifier = Modifier.align(Alignment.CenterHorizontally),
             )
         }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 31201c2f..15e7b51 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -347,9 +347,6 @@
                         viewModel = footerActionsViewModel,
                         isCustomizing = isCustomizing,
                         lifecycleOwner = lifecycleOwner,
-                        footerActionsModifier = { modifier ->
-                            modifier.element(QuickSettings.Elements.FooterActions)
-                        },
                         modifier = Modifier.align(Alignment.CenterHorizontally),
                     )
                 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
index 5f7bd47..b721e41 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
@@ -32,6 +32,11 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.Expandable
@@ -52,6 +57,7 @@
     override fun VolumePanelComposeScope.Content(modifier: Modifier) {
         val viewModelByState by viewModelFlow.collectAsState()
         val viewModel = viewModelByState ?: return
+        val label = viewModel.label.toString()
 
         Column(
             modifier = modifier,
@@ -59,7 +65,11 @@
             horizontalAlignment = Alignment.CenterHorizontally,
         ) {
             Expandable(
-                modifier = Modifier.height(64.dp).fillMaxWidth(),
+                modifier =
+                    Modifier.height(64.dp).fillMaxWidth().semantics {
+                        role = Role.Button
+                        contentDescription = label
+                    },
                 color = MaterialTheme.colorScheme.primaryContainer,
                 shape = RoundedCornerShape(28.dp),
                 contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
@@ -71,7 +81,8 @@
                 }
             }
             Text(
-                text = viewModel.label.toString(),
+                modifier = Modifier.clearAndSetSemantics {},
+                text = label,
                 style = MaterialTheme.typography.labelMedium,
                 maxLines = 1,
                 overflow = TextOverflow.Ellipsis,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
index dfee684..28fd785 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
@@ -32,6 +32,9 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import com.android.systemui.common.ui.compose.Icon
@@ -50,13 +53,16 @@
     override fun VolumePanelComposeScope.Content(modifier: Modifier) {
         val viewModelByState by viewModelFlow.collectAsState()
         val viewModel = viewModelByState ?: return
+        val label = viewModel.label.toString()
+
         Column(
             modifier = modifier,
             verticalArrangement = Arrangement.spacedBy(12.dp),
             horizontalAlignment = Alignment.CenterHorizontally,
         ) {
             OutlinedIconToggleButton(
-                modifier = Modifier.height(64.dp).fillMaxWidth(),
+                modifier =
+                    Modifier.height(64.dp).fillMaxWidth().semantics { contentDescription = label },
                 checked = viewModel.isChecked,
                 onCheckedChange = onCheckedChange,
                 shape = RoundedCornerShape(28.dp),
@@ -72,7 +78,8 @@
                 Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
             }
             Text(
-                text = viewModel.label.toString(),
+                modifier = Modifier.clearAndSetSemantics {},
+                text = label,
                 style = MaterialTheme.typography.labelMedium,
                 maxLines = 1,
                 overflow = TextOverflow.Ellipsis,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
index 53de5bc..6f2ed81 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
@@ -49,10 +49,16 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.LiveRegionMode
+import androidx.compose.ui.semantics.liveRegion
+import androidx.compose.ui.semantics.onClick
+import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.Expandable
 import com.android.systemui.common.ui.compose.Icon
 import com.android.systemui.common.ui.compose.toColor
+import com.android.systemui.res.R
 import com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel.ConnectedDeviceViewModel
 import com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel.DeviceIconViewModel
 import com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel.MediaOutputViewModel
@@ -74,14 +80,19 @@
             viewModel.connectedDeviceViewModel.collectAsState()
         val deviceIconViewModel: DeviceIconViewModel? by
             viewModel.deviceIconViewModel.collectAsState()
+        val clickLabel = stringResource(R.string.volume_panel_enter_media_output_settings)
 
         Expandable(
-            modifier = Modifier.fillMaxWidth().height(80.dp),
+            modifier =
+                Modifier.fillMaxWidth().height(80.dp).semantics {
+                    liveRegion = LiveRegionMode.Polite
+                    this.onClick(label = clickLabel) { false }
+                },
             color = MaterialTheme.colorScheme.surface,
             shape = RoundedCornerShape(28.dp),
             onClick = { viewModel.onBarClick(it) },
         ) { _ ->
-            Row(verticalAlignment = Alignment.CenterVertically) {
+            Row(modifier = Modifier, verticalAlignment = Alignment.CenterVertically) {
                 connectedDeviceViewModel?.let { ConnectedDeviceText(it) }
 
                 deviceIconViewModel?.let { ConnectedDeviceIcon(it) }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
index 89251939..26086d1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
@@ -24,13 +24,16 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
 import androidx.compose.material3.IconButtonDefaults
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
-import com.android.compose.PlatformIconButton
 import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.animation.Expandable
 import com.android.systemui.res.R
@@ -101,16 +104,19 @@
                 }
             }
 
-            PlatformIconButton(
+            IconButton(
                 modifier = Modifier.align(Alignment.TopEnd).size(64.dp).padding(20.dp),
-                iconResource = R.drawable.ic_close,
-                contentDescription = null,
                 onClick = { dialog.dismiss() },
                 colors =
                     IconButtonDefaults.iconButtonColors(
                         contentColor = MaterialTheme.colorScheme.outline
                     )
-            )
+            ) {
+                Icon(
+                    painterResource(R.drawable.ic_close),
+                    contentDescription = stringResource(R.string.accessibility_desc_close),
+                )
+            }
         }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
index 81d2da0..e1cf3a5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
@@ -45,6 +45,11 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.stateDescription
 import androidx.compose.ui.unit.dp
 import com.android.compose.PlatformSliderColors
 import com.android.systemui.res.R
@@ -76,16 +81,26 @@
             VolumeSlider(
                 modifier = Modifier.weight(1f),
                 state = sliderState,
-                onValueChangeFinished = { sliderViewModel.onValueChangeFinished(sliderState, it) },
+                onValueChange = { newValue: Float ->
+                    sliderViewModel.onValueChanged(sliderState, newValue)
+                },
+                onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
                 sliderColors = sliderColors,
             )
 
+            val expandButtonStateDescription =
+                if (isExpanded) stringResource(R.string.volume_panel_expanded_sliders)
+                else stringResource(R.string.volume_panel_collapsed_sliders)
             if (isExpandable) {
                 ExpandButton(
+                    modifier =
+                        Modifier.semantics {
+                            role = Role.Switch
+                            stateDescription = expandButtonStateDescription
+                        },
                     isExpanded = isExpanded,
                     onExpandedChanged = onExpandedChanged,
-                    sliderColors,
-                    Modifier,
+                    sliderColors = sliderColors,
                 )
             }
         }
@@ -114,9 +129,10 @@
                         VolumeSlider(
                             modifier = Modifier.fillMaxWidth().padding(top = 16.dp),
                             state = sliderState,
-                            onValueChangeFinished = {
-                                sliderViewModel.onValueChangeFinished(sliderState, it)
+                            onValueChange = { newValue: Float ->
+                                sliderViewModel.onValueChanged(sliderState, newValue)
                             },
+                            onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
                             sliderColors = sliderColors,
                         )
                     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
index 910ee72..b284c69 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
@@ -43,7 +43,10 @@
             VolumeSlider(
                 modifier = Modifier.fillMaxWidth(),
                 state = sliderState,
-                onValueChangeFinished = { sliderViewModel.onValueChangeFinished(sliderState, it) },
+                onValueChange = { newValue: Float ->
+                    sliderViewModel.onValueChanged(sliderState, newValue)
+                },
+                onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
                 sliderColors = sliderColors,
             )
         }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index 3e0aee5..fa94ea0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -18,19 +18,27 @@
 
 import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.animation.animateContentSize
-import androidx.compose.animation.expandVertically
-import androidx.compose.animation.shrinkVertically
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutVertically
 import androidx.compose.foundation.basicMarquee
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.size
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.IconButtonColors
+import androidx.compose.material3.LocalContentColor
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableFloatStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.semantics.ProgressBarRangeInfo
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.disabled
+import androidx.compose.ui.semantics.progressBarRangeInfo
+import androidx.compose.ui.semantics.setProgress
 import androidx.compose.ui.unit.dp
 import com.android.compose.PlatformSlider
 import com.android.compose.PlatformSliderColors
@@ -40,23 +48,61 @@
 @Composable
 fun VolumeSlider(
     state: SliderState,
-    onValueChangeFinished: (Float) -> Unit,
+    onValueChange: (newValue: Float) -> Unit,
+    onIconTapped: () -> Unit,
     modifier: Modifier = Modifier,
     sliderColors: PlatformSliderColors,
 ) {
-    var value by remember(state.value) { mutableFloatStateOf(state.value) }
+    val value by
+        animateFloatAsState(targetValue = state.value, label = "VolumeSliderValueAnimation")
     PlatformSlider(
-        modifier = modifier,
+        modifier =
+            modifier.clearAndSetSemantics {
+                if (!state.isEnabled) disabled()
+                contentDescription = state.label
+
+                // provide a not animated value to the a11y because it fails to announce the settled
+                // value when it changes rapidly.
+                progressBarRangeInfo = ProgressBarRangeInfo(state.value, state.valueRange)
+                setProgress { targetValue ->
+                    val targetDirection =
+                        when {
+                            targetValue > value -> 1
+                            targetValue < value -> -1
+                            else -> 0
+                        }
+
+                    val newValue =
+                        (value + targetDirection * state.a11yStep).coerceIn(
+                            state.valueRange.start,
+                            state.valueRange.endInclusive
+                        )
+                    onValueChange(newValue)
+                    true
+                }
+            },
         value = value,
         valueRange = state.valueRange,
-        onValueChange = { value = it },
-        onValueChangeFinished = { onValueChangeFinished(value) },
+        onValueChange = onValueChange,
         enabled = state.isEnabled,
         icon = { isDragging ->
             if (isDragging) {
-                Text(text = value.toInt().toString())
+                Text(text = value.toInt().toString(), color = LocalContentColor.current)
             } else {
-                state.icon?.let { Icon(modifier = Modifier.size(24.dp), icon = it) }
+                state.icon?.let {
+                    IconButton(
+                        onClick = onIconTapped,
+                        colors =
+                            IconButtonColors(
+                                contentColor = LocalContentColor.current,
+                                containerColor = Color.Transparent,
+                                disabledContentColor = LocalContentColor.current,
+                                disabledContainerColor = Color.Transparent,
+                            )
+                    ) {
+                        Icon(modifier = Modifier.size(24.dp), icon = it)
+                    }
+                }
             }
         },
         colors = sliderColors,
@@ -66,19 +112,21 @@
                     modifier = Modifier.basicMarquee(),
                     text = state.label,
                     style = MaterialTheme.typography.titleMedium,
+                    color = LocalContentColor.current,
                     maxLines = 1,
                 )
 
                 state.disabledMessage?.let { message ->
                     AnimatedVisibility(
                         !state.isEnabled,
-                        enter = expandVertically { it },
-                        exit = shrinkVertically { it },
+                        enter = slideInVertically { it },
+                        exit = slideOutVertically { it },
                     ) {
                         Text(
                             modifier = Modifier.basicMarquee(),
                             text = message,
                             style = MaterialTheme.typography.bodySmall,
+                            color = LocalContentColor.current,
                             maxLines = 1,
                         )
                     }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 63ec54f..82083f9 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
@@ -605,6 +605,8 @@
 
     override val isInitiatedByUserInput = true
 
+    override var bouncingScene: SceneKey? = null
+
     /** The current offset caused by the drag gesture. */
     var dragOffset by mutableFloatStateOf(0f)
 
@@ -694,14 +696,31 @@
     ): OffsetAnimation {
         return startOffsetAnimation {
             val animatable = Animatable(dragOffset, OffsetVisibilityThreshold)
+            val isTargetGreater = targetOffset > animatable.value
             val job =
                 coroutineScope
                     .launch {
-                        animatable.animateTo(
-                            targetValue = targetOffset,
-                            animationSpec = swipeSpec,
-                            initialVelocity = initialVelocity,
-                        )
+                        try {
+                            animatable.animateTo(
+                                targetValue = targetOffset,
+                                animationSpec = swipeSpec,
+                                initialVelocity = initialVelocity,
+                            ) {
+                                if (bouncingScene == null) {
+                                    val isBouncing =
+                                        if (isTargetGreater) {
+                                            value > targetOffset
+                                        } else {
+                                            value < targetOffset
+                                        }
+                                    if (isBouncing) {
+                                        bouncingScene = targetScene
+                                    }
+                                }
+                            }
+                        } finally {
+                            bouncingScene = null
+                        }
                     }
                     // Make sure that we settle to target scene at the end of the animation or if
                     // the animation is cancelled.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index c7186da..f1177a8 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -588,7 +588,8 @@
             // TODO(b/290184746): Make sure that we don't overflow transformations associated to a
             // range.
             val directionSign = if (transition.isUpOrLeft) -1 else 1
-            val overscrollProgress = transition.progress.let { if (it > 1f) it - 1f else it }
+            val isToScene = overscroll.scene == transition.toScene
+            val overscrollProgress = transition.progress.let { if (isToScene) it - 1f else it }
             val progress = directionSign * overscrollProgress
             val rangeProgress = propertySpec.range?.progress(progress) ?: progress
 
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 e6f5d58..617a8ea 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
@@ -255,6 +255,12 @@
          */
         val overscrollScope: OverscrollScope
 
+        /**
+         * The scene around which the transition is currently bouncing. When not `null`, this
+         * transition is currently oscillating around this scene and will soon settle to that scene.
+         */
+        val bouncingScene: SceneKey?
+
         companion object {
             const val DistanceUnspecified = 0f
         }
@@ -287,9 +293,10 @@
             val transition = currentTransition ?: return null
             if (transition !is TransitionState.HasOverscrollProperties) return null
             val progress = transition.progress
+            val bouncingScene = transition.bouncingScene
             return when {
-                progress < 0f -> fromOverscrollSpec
-                progress > 1f -> toOverscrollSpec
+                progress < 0f || bouncingScene == transition.fromScene -> fromOverscrollSpec
+                progress > 1f || bouncingScene == transition.toScene -> toOverscrollSpec
                 else -> null
             }
         }
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 26e01fe..0804761 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
@@ -16,6 +16,8 @@
 
 package com.android.compose.animation.scene
 
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.spring
 import androidx.compose.animation.core.tween
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.gestures.Orientation
@@ -752,4 +754,55 @@
         assertThat(state.currentOverscrollSpec).isNotNull()
         fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * 1.5f)
     }
+
+    @Test
+    fun elementTransitionWithDistanceDuringOverscrollBouncing() {
+        val layoutWidth = 200.dp
+        val layoutHeight = 400.dp
+        val state =
+            setupOverscrollScenario(
+                layoutWidth = layoutWidth,
+                layoutHeight = layoutHeight,
+                sceneTransitions = {
+                    defaultSwipeSpec =
+                        spring(
+                            dampingRatio = Spring.DampingRatioMediumBouncy,
+                            stiffness = Spring.StiffnessLow,
+                        )
+
+                    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()
+        transition as TransitionState.HasOverscrollProperties
+
+        // Scroll 150% (100% scroll + 50% overscroll)
+        assertThat(transition.progress).isEqualTo(1.5f)
+        assertThat(state.currentOverscrollSpec).isNotNull()
+        fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * (transition.progress - 1f))
+
+        // finger raised
+        rule.onRoot().performTouchInput { up() }
+
+        // The target value is 1f, but the spring (defaultSwipeSpec) allows you to go to a lower
+        // value.
+        rule.waitUntil(timeoutMillis = 10_000) { transition.progress < 1f }
+
+        assertThat(state.currentOverscrollSpec).isNotNull()
+        assertThat(transition.bouncingScene).isEqualTo(transition.toScene)
+    }
 }
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 73a66c6..a32fe22 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
@@ -27,6 +27,7 @@
     isInitiatedByUserInput: Boolean = false,
     isUserInputOngoing: Boolean = false,
     isUpOrLeft: Boolean = false,
+    bouncingScene: SceneKey? = null,
     orientation: Orientation = Orientation.Horizontal,
 ): TransitionState.Transition {
     return object : TransitionState.Transition(from, to), TransitionState.HasOverscrollProperties {
@@ -37,6 +38,7 @@
         override val isInitiatedByUserInput: Boolean = isInitiatedByUserInput
         override val isUserInputOngoing: Boolean = isUserInputOngoing
         override val isUpOrLeft: Boolean = isUpOrLeft
+        override val bouncingScene: SceneKey? = bouncingScene
         override val orientation: Orientation = orientation
         override val overscrollScope: OverscrollScope =
             object : OverscrollScope {
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/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index ce6445b..43266bf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.communal
 
+import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -25,13 +26,17 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.dock.dockManager
 import com.android.systemui.dock.fakeDockManager
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
+import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.TestScope
@@ -54,11 +59,15 @@
     @Before
     fun setUp() {
         with(kosmos) {
+            fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT)
+
             underTest =
                 CommunalSceneStartable(
                         dockManager = dockManager,
                         communalInteractor = communalInteractor,
                         keyguardTransitionInteractor = keyguardTransitionInteractor,
+                        keyguardInteractor = keyguardInteractor,
+                        systemSettings = fakeSettings,
                         applicationScope = applicationCoroutineScope,
                         bgScope = applicationCoroutineScope,
                     )
@@ -246,6 +255,132 @@
             }
         }
 
+    @Test
+    fun hubTimeout_whenDreaming_goesToBlank() =
+        with(kosmos) {
+            testScope.runTest {
+                // Device is dreaming and on communal.
+                fakeKeyguardRepository.setDreaming(true)
+                communalInteractor.onSceneChanged(CommunalScenes.Communal)
+
+                val scene by collectLastValue(communalInteractor.desiredScene)
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+                // Scene times out back to blank after the screen timeout.
+                advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+                assertThat(scene).isEqualTo(CommunalScenes.Blank)
+            }
+        }
+
+    @Test
+    fun hubTimeout_notDreaming_staysOnCommunal() =
+        with(kosmos) {
+            testScope.runTest {
+                // Device is not dreaming and on communal.
+                fakeKeyguardRepository.setDreaming(false)
+                communalInteractor.onSceneChanged(CommunalScenes.Communal)
+
+                // Scene stays as Communal
+                advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+                val scene by collectLastValue(communalInteractor.desiredScene)
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+            }
+        }
+
+    @Test
+    fun hubTimeout_dreamStopped_staysOnCommunal() =
+        with(kosmos) {
+            testScope.runTest {
+                // Device is dreaming and on communal.
+                fakeKeyguardRepository.setDreaming(true)
+                communalInteractor.onSceneChanged(CommunalScenes.Communal)
+
+                val scene by collectLastValue(communalInteractor.desiredScene)
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+                // Wait a bit, but not long enough to timeout.
+                advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+                // Dream stops, timeout is cancelled and device stays on hub, because the regular
+                // screen timeout will take effect at this point.
+                fakeKeyguardRepository.setDreaming(false)
+                advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+            }
+        }
+
+    @Test
+    fun hubTimeout_dreamStartedHalfway_goesToCommunal() =
+        with(kosmos) {
+            testScope.runTest {
+                // Device is on communal, but not dreaming.
+                fakeKeyguardRepository.setDreaming(false)
+                communalInteractor.onSceneChanged(CommunalScenes.Communal)
+
+                val scene by collectLastValue(communalInteractor.desiredScene)
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+                // Wait a bit, but not long enough to timeout, then start dreaming.
+                advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+                fakeKeyguardRepository.setDreaming(true)
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+                // Device times out after one screen timeout interval, dream doesn't reset timeout.
+                advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+                assertThat(scene).isEqualTo(CommunalScenes.Blank)
+            }
+        }
+
+    @Test
+    fun hubTimeout_userActivityTriggered_resetsTimeout() =
+        with(kosmos) {
+            testScope.runTest {
+                // Device is dreaming and on communal.
+                fakeKeyguardRepository.setDreaming(true)
+                communalInteractor.onSceneChanged(CommunalScenes.Communal)
+
+                val scene by collectLastValue(communalInteractor.desiredScene)
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+                // Wait a bit, but not long enough to timeout.
+                advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+
+                // Send user interaction to reset timeout.
+                communalInteractor.signalUserInteraction()
+
+                // If user activity didn't reset timeout, we would have gone back to Blank by now.
+                advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+                // Timeout happens one interval after the user interaction.
+                advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+                assertThat(scene).isEqualTo(CommunalScenes.Blank)
+            }
+        }
+
+    @Test
+    fun hubTimeout_screenTimeoutChanged() =
+        with(kosmos) {
+            testScope.runTest {
+                fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT * 2)
+
+                // Device is dreaming and on communal.
+                fakeKeyguardRepository.setDreaming(true)
+                communalInteractor.onSceneChanged(CommunalScenes.Communal)
+
+                val scene by collectLastValue(communalInteractor.desiredScene)
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+                // Scene times out back to blank after the screen timeout.
+                advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+                advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+                assertThat(scene).isEqualTo(CommunalScenes.Blank)
+            }
+        }
+
     private fun TestScope.updateDocked(docked: Boolean) =
         with(kosmos) {
             runCurrent()
@@ -260,4 +395,8 @@
             setCommunalAvailable(true)
             runCurrent()
         }
+
+    companion object {
+        private const val SCREEN_TIMEOUT = 1000
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/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/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 24c651f3..a9541d9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -61,6 +61,7 @@
     private val testScope = kosmos.testScope
     private val repository by lazy { kosmos.fakeKeyguardRepository }
     private val sceneInteractor by lazy { kosmos.sceneInteractor }
+    private val fromGoneTransitionInteractor by lazy { kosmos.fromGoneTransitionInteractor }
     private val commandQueue by lazy { FakeCommandQueue() }
     private val bouncerRepository = FakeKeyguardBouncerRepository()
     private val shadeRepository = FakeShadeRepository()
@@ -79,6 +80,7 @@
             shadeRepository = shadeRepository,
             keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
             sceneInteractorProvider = { sceneInteractor },
+            fromGoneTransitionInteractor = { fromGoneTransitionInteractor },
         )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index cd4db2f..769caaa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -47,9 +47,7 @@
 @kotlinx.coroutines.ExperimentalCoroutinesApi
 @android.platform.test.annotations.EnabledOnRavenwood
 class KeyguardTransitionInteractorTest : SysuiTestCase() {
-
     val kosmos = testKosmos()
-
     val underTest = kosmos.keyguardTransitionInteractor
     val repository = kosmos.fakeKeyguardTransitionRepository
     val testScope = kosmos.testScope
@@ -242,34 +240,35 @@
     }
 
     @Test
-    fun transitionValue() = runTest {
-        val startedSteps by collectValues(underTest.transitionValue(state = DOZING))
+    fun transitionValue() =
+        testScope.runTest {
+            val startedSteps by collectValues(underTest.transitionValue(state = DOZING))
 
-        val toSteps =
-            listOf(
-                TransitionStep(AOD, DOZING, 0f, STARTED),
-                TransitionStep(AOD, DOZING, 0.5f, RUNNING),
-                TransitionStep(AOD, DOZING, 1f, FINISHED),
-            )
-        toSteps.forEach {
-            repository.sendTransitionStep(it)
-            runCurrent()
+            val toSteps =
+                listOf(
+                    TransitionStep(AOD, DOZING, 0f, STARTED),
+                    TransitionStep(AOD, DOZING, 0.5f, RUNNING),
+                    TransitionStep(AOD, DOZING, 1f, FINISHED),
+                )
+            toSteps.forEach {
+                repository.sendTransitionStep(it)
+                runCurrent()
+            }
+
+            val fromSteps =
+                listOf(
+                    TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED),
+                    TransitionStep(DOZING, LOCKSCREEN, 0.5f, RUNNING),
+                    TransitionStep(DOZING, LOCKSCREEN, 1f, FINISHED),
+                )
+            fromSteps.forEach {
+                repository.sendTransitionStep(it)
+                runCurrent()
+            }
+
+            assertThat(startedSteps).isEqualTo(listOf(0f, 0.5f, 1f, 1f, 0.5f, 0f))
         }
 
-        val fromSteps =
-            listOf(
-                TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED),
-                TransitionStep(DOZING, LOCKSCREEN, 0.5f, RUNNING),
-                TransitionStep(DOZING, LOCKSCREEN, 1f, FINISHED),
-            )
-        fromSteps.forEach {
-            repository.sendTransitionStep(it)
-            runCurrent()
-        }
-
-        assertThat(startedSteps).isEqualTo(listOf(0f, 0.5f, 1f, 1f, 0.5f, 0f))
-    }
-
     @Test
     fun isInTransitionToAnyState() =
         testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
index de659cf..f517cec 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
@@ -81,12 +81,12 @@
     }
 
     @Test
-    fun movement_initializedToZero() =
+    fun movement_initializedToDefaultValues() =
         testScope.runTest {
             val movement by collectLastValue(underTest.movement(burnInParameters))
             assertThat(movement?.translationY).isEqualTo(0)
             assertThat(movement?.translationX).isEqualTo(0)
-            assertThat(movement?.scale).isEqualTo(0f)
+            assertThat(movement?.scale).isEqualTo(1f)
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt
index 64125f1..af96780 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt
@@ -99,6 +99,31 @@
             values.forEach { assertThat(it.value).isIn(Range.closed(-100f, 0f)) }
         }
 
+    @Test
+    fun lockscreenTranslationX_resetsAfterCancellation() =
+        testScope.runTest {
+            configurationRepository.setDimensionPixelSize(
+                R.dimen.hub_to_lockscreen_transition_lockscreen_translation_x,
+                100
+            )
+            val values by collectValues(underTest.keyguardTranslationX)
+            assertThat(values).isEmpty()
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                listOf(
+                    step(0f, TransitionState.STARTED),
+                    step(0.3f),
+                    step(0.5f),
+                    step(0.9f, TransitionState.CANCELED)
+                ),
+                testScope,
+            )
+
+            assertThat(values).hasSize(4)
+            values.forEach { assertThat(it.value).isIn(Range.closed(-100f, 0f)) }
+            assertThat(values.last().value).isEqualTo(0f)
+        }
+
     private fun step(
         value: Float,
         state: TransitionState = TransitionState.RUNNING
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 979d504..6e15c51 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -86,6 +86,13 @@
     }
 
     @Test
+    fun defaultBurnInScaleEqualsOne() =
+        testScope.runTest {
+            val burnInScale by collectLastValue(underTest.scale)
+            assertThat(burnInScale!!.scale).isEqualTo(1f)
+        }
+
+    @Test
     fun burnInLayerVisibility() =
         testScope.runTest {
             val burnInLayerVisibility by collectLastValue(underTest.burnInLayerVisibility)
@@ -250,6 +257,17 @@
 
             keyguardRepository.topClippingBounds.value = 1000
             assertThat(topClippingBounds).isEqualTo(1000)
+
+            // Run at least 1 transition to make sure value remains at 0
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.AOD,
+                to = KeyguardState.GONE,
+                testScope,
+            )
+
+            // Make sure the value hasn't changed since we're GONE
+            keyguardRepository.topClippingBounds.value = 5
+            assertThat(topClippingBounds).isEqualTo(1000)
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/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/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index e683f34c..53a8e5d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.common.shared.model.NotificationContainerBounds
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -38,6 +39,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,41 +701,57 @@
         }
 
     @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)
+            val fadeIn by collectValues(underTest.shadeCollapseFadeIn)
 
             // Start on lockscreen without the shade
-            underTest.setShadeCollapseFadeInComplete(false)
             showLockscreen()
-            assertThat(fadeIn).isEqualTo(false)
+            assertThat(fadeIn[0]).isEqualTo(false)
 
             // ... then the shade expands
             showLockscreenWithShadeExpanded()
-            assertThat(fadeIn).isEqualTo(false)
+            assertThat(fadeIn[0]).isEqualTo(false)
 
             // ... it collapses
             showLockscreen()
-            assertThat(fadeIn).isEqualTo(true)
+            assertThat(fadeIn[1]).isEqualTo(true)
 
-            // ... now send animation complete signal
-            underTest.setShadeCollapseFadeInComplete(true)
-            assertThat(fadeIn).isEqualTo(false)
+            // ... and ensure the value goes back to false
+            assertThat(fadeIn[2]).isEqualTo(false)
         }
 
     @Test
     fun shadeCollapseFadeIn_doesNotRunIfTransitioningToAod() =
         testScope.runTest {
-            val fadeIn by collectLastValue(underTest.shadeCollapseFadeIn)
+            val fadeIn by collectValues(underTest.shadeCollapseFadeIn)
 
             // Start on lockscreen without the shade
-            underTest.setShadeCollapseFadeInComplete(false)
             showLockscreen()
-            assertThat(fadeIn).isEqualTo(false)
+            assertThat(fadeIn[0]).isEqualTo(false)
 
             // ... then the shade expands
             showLockscreenWithShadeExpanded()
-            assertThat(fadeIn).isEqualTo(false)
+            assertThat(fadeIn[0]).isEqualTo(false)
 
             // ... then user hits power to go to AOD
             keyguardTransitionRepository.sendTransitionSteps(
@@ -744,7 +762,7 @@
             // ... followed by a shade collapse
             showLockscreen()
             // ... does not trigger a fade in
-            assertThat(fadeIn).isEqualTo(false)
+            assertThat(fadeIn[0]).isEqualTo(false)
         }
 
     private suspend fun TestScope.showLockscreen() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
new file mode 100644
index 0000000..183a58a
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.policy
+
+import android.app.Notification
+import android.platform.test.annotations.EnableFlags
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.settings.FakeGlobalSettings
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.invocation.InvocationOnMock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@SmallTest
+@RunWithLooper
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(NotificationThrottleHun.FLAG_NAME)
+class AvalancheControllerTest : SysuiTestCase() {
+
+    private val mAvalancheController = AvalancheController()
+
+    // For creating mocks
+    @get:Rule var rule: MockitoRule = MockitoJUnit.rule()
+    @Mock private val runnableMock: Runnable? = null
+
+    // For creating TestableHeadsUpManager
+    @Mock private val mAccessibilityMgr: AccessibilityManagerWrapper? = null
+    private val mUiEventLoggerFake = UiEventLoggerFake()
+    private val mLogger = Mockito.spy(HeadsUpManagerLogger(logcatLogBuffer()))
+    private val mGlobalSettings = FakeGlobalSettings()
+    private val mSystemClock = FakeSystemClock()
+    private val mExecutor = FakeExecutor(mSystemClock)
+    private var testableHeadsUpManager: BaseHeadsUpManager? = null
+
+    @Before
+    fun setUp() {
+        // Use default non-a11y timeout
+        Mockito.`when`(
+                mAccessibilityMgr!!.getRecommendedTimeoutMillis(
+                    ArgumentMatchers.anyInt(),
+                    ArgumentMatchers.anyInt()
+                )
+            )
+            .then { i: InvocationOnMock -> i.getArgument(0) }
+
+        // Initialize TestableHeadsUpManager here instead of at declaration, when mocks will be null
+        testableHeadsUpManager =
+            TestableHeadsUpManager(
+                mContext,
+                mLogger,
+                mExecutor,
+                mGlobalSettings,
+                mSystemClock,
+                mAccessibilityMgr,
+                mUiEventLoggerFake,
+                mAvalancheController
+            )
+    }
+
+    private fun createHeadsUpEntry(id: Int): BaseHeadsUpManager.HeadsUpEntry {
+        val entry = testableHeadsUpManager!!.createHeadsUpEntry()
+
+        entry.setEntry(
+            NotificationEntryBuilder()
+                .setSbn(HeadsUpManagerTestUtil.createSbn(id, Notification.Builder(mContext, "")))
+                .build()
+        )
+        return entry
+    }
+
+    @Test
+    fun testUpdate_isShowing_runsRunnable() {
+        // Entry is showing
+        val headsUpEntry = createHeadsUpEntry(id = 0)
+        mAvalancheController.headsUpEntryShowing = headsUpEntry
+
+        // Update
+        mAvalancheController.update(headsUpEntry, runnableMock!!, "testLabel")
+
+        // Runnable was run
+        Mockito.verify(runnableMock, Mockito.times(1)).run()
+    }
+
+    @Test
+    fun testUpdate_noneShowingAndNotNext_showNow() {
+        val headsUpEntry = createHeadsUpEntry(id = 0)
+
+        // None showing
+        mAvalancheController.headsUpEntryShowing = null
+
+        // Entry is NOT next
+        mAvalancheController.clearNext()
+
+        // Update
+        mAvalancheController.update(headsUpEntry, runnableMock!!, "testLabel")
+
+        // Entry is showing now
+        Truth.assertThat(mAvalancheController.headsUpEntryShowing).isEqualTo(headsUpEntry)
+    }
+
+    @Test
+    fun testUpdate_isNext_addsRunnable() {
+        // Another entry is already showing
+        val otherShowingEntry = createHeadsUpEntry(id = 0)
+        mAvalancheController.headsUpEntryShowing = otherShowingEntry
+
+        // Entry is next
+        val headsUpEntry = createHeadsUpEntry(id = 1)
+        mAvalancheController.addToNext(headsUpEntry, runnableMock!!)
+
+        // Entry has one Runnable
+        val runnableList: List<Runnable?>? = mAvalancheController.nextMap[headsUpEntry]
+        Truth.assertThat(runnableList).isNotNull()
+        Truth.assertThat(runnableList!!.size).isEqualTo(1)
+
+        // Update
+        mAvalancheController.update(headsUpEntry, runnableMock, "testLabel")
+
+        // Entry has two Runnables
+        Truth.assertThat(runnableList.size).isEqualTo(2)
+    }
+
+    @Test
+    fun testUpdate_isNotNextWithOtherHunShowing_isNext() {
+        val headsUpEntry = createHeadsUpEntry(id = 0)
+
+        // Another entry is already showing
+        val otherShowingEntry = createHeadsUpEntry(id = 1)
+        mAvalancheController.headsUpEntryShowing = otherShowingEntry
+
+        // Entry is NOT next
+        mAvalancheController.clearNext()
+
+        // Update
+        mAvalancheController.update(headsUpEntry, runnableMock!!, "testLabel")
+
+        // Entry is next
+        Truth.assertThat(mAvalancheController.nextMap.containsKey(headsUpEntry)).isTrue()
+    }
+
+    @Test
+    fun testDelete_isNext_removedFromNext_runnableNotRun() {
+        // Entry is next
+        val headsUpEntry = createHeadsUpEntry(id = 0)
+        mAvalancheController.addToNext(headsUpEntry, runnableMock!!)
+
+        // Delete
+        mAvalancheController.delete(headsUpEntry, runnableMock, "testLabel")
+
+        // Entry was removed from next
+        Truth.assertThat(mAvalancheController.nextMap.containsKey(headsUpEntry)).isFalse()
+
+        // Runnable was not run
+        Mockito.verify(runnableMock, Mockito.times(0)).run()
+    }
+
+    @Test
+    fun testDelete_wasDropped_removedFromDropSet() {
+        // Entry was dropped
+        val headsUpEntry = createHeadsUpEntry(id = 0)
+        mAvalancheController.debugDropSet.add(headsUpEntry)
+
+        // Delete
+        mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel")
+
+        // Entry was removed from dropSet
+        Truth.assertThat(mAvalancheController.debugDropSet.contains(headsUpEntry)).isFalse()
+    }
+
+    @Test
+    fun testDelete_wasDropped_runnableNotRun() {
+        // Entry was dropped
+        val headsUpEntry = createHeadsUpEntry(id = 0)
+        mAvalancheController.debugDropSet.add(headsUpEntry)
+
+        // Delete
+        mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel")
+
+        // Runnable was not run
+        Mockito.verify(runnableMock, Mockito.times(0)).run()
+    }
+
+    @Test
+    fun testDelete_isShowing_runnableRun() {
+        // Entry is showing
+        val headsUpEntry = createHeadsUpEntry(id = 0)
+        mAvalancheController.headsUpEntryShowing = headsUpEntry
+
+        // Delete
+        mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel")
+
+        // Runnable was run
+        Mockito.verify(runnableMock, Mockito.times(1)).run()
+    }
+
+    @Test
+    fun testDelete_isShowing_showNext() {
+        // Entry is showing
+        val showingEntry = createHeadsUpEntry(id = 0)
+        mAvalancheController.headsUpEntryShowing = showingEntry
+
+        // There's another entry waiting to show next
+        val nextEntry = createHeadsUpEntry(id = 1)
+        mAvalancheController.addToNext(nextEntry, runnableMock!!)
+
+        // Delete
+        mAvalancheController.delete(showingEntry, runnableMock, "testLabel")
+
+        // Next entry is shown
+        Truth.assertThat(mAvalancheController.headsUpEntryShowing).isEqualTo(nextEntry)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
index db4d42f..830bcef 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
@@ -35,13 +35,10 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.ActivityManager;
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.app.Person;
 import android.content.Intent;
-import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
 import android.testing.TestableLooper;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -77,6 +74,8 @@
 
     private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
     private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
+    private AvalancheController mAvalancheController = new AvalancheController();
+
     @Mock private AccessibilityManagerWrapper mAccessibilityMgr;
 
     protected static final int TEST_MINIMUM_DISPLAY_TIME = 400;
@@ -99,7 +98,7 @@
 
     private BaseHeadsUpManager createHeadsUpManager() {
         return new TestableHeadsUpManager(mContext, mLogger, mExecutor, mGlobalSettings,
-                mSystemClock, mAccessibilityMgr, mUiEventLoggerFake);
+                mSystemClock, mAccessibilityMgr, mUiEventLoggerFake, mAvalancheController);
     }
 
     private NotificationEntry createStickyEntry(int id) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
index c032d7c..61a79d8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
@@ -28,8 +28,8 @@
 import android.content.Context;
 import android.testing.TestableLooper;
 
-import androidx.test.filters.SmallTest;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -76,6 +76,7 @@
     @Mock private UiEventLogger mUiEventLogger;
     @Mock private JavaAdapter mJavaAdapter;
     @Mock private ShadeInteractor mShadeInteractor;
+    private AvalancheController mAvalancheController = new AvalancheController();
 
     private static final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone {
         TestableHeadsUpManagerPhone(
@@ -92,7 +93,8 @@
                 AccessibilityManagerWrapper accessibilityManagerWrapper,
                 UiEventLogger uiEventLogger,
                 JavaAdapter javaAdapter,
-                ShadeInteractor shadeInteractor
+                ShadeInteractor shadeInteractor,
+                AvalancheController avalancheController
         ) {
             super(
                     context,
@@ -109,7 +111,8 @@
                     accessibilityManagerWrapper,
                     uiEventLogger,
                     javaAdapter,
-                    shadeInteractor
+                    shadeInteractor,
+                    avalancheController
             );
             mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
             mAutoDismissTime = TEST_AUTO_DISMISS_TIME;
@@ -131,12 +134,15 @@
                 mAccessibilityManagerWrapper,
                 mUiEventLogger,
                 mJavaAdapter,
-                mShadeInteractor
+                mShadeInteractor,
+                mAvalancheController
         );
     }
 
     @Before
     public void setUp() {
+        // TODO(b/315362456) create separate test with the flag disabled
+        //  then modify this file to test with the flag enabled
         mSetFlagsRule.disableFlags(NotificationThrottleHun.FLAG_NAME);
 
         when(mShadeInteractor.isAnyExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false));
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
index 2747629..d8f77f0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
@@ -43,9 +43,10 @@
             GlobalSettings globalSettings,
             SystemClock systemClock,
             AccessibilityManagerWrapper accessibilityManagerWrapper,
-            UiEventLogger uiEventLogger) {
+            UiEventLogger uiEventLogger,
+            AvalancheController avalancheController) {
         super(context, logger, mockExecutorHandler(executor), globalSettings, systemClock,
-                executor, accessibilityManagerWrapper, uiEventLogger);
+                executor, accessibilityManagerWrapper, uiEventLogger, avalancheController);
 
         mTouchAcceptanceDelay = BaseHeadsUpManagerTest.TEST_TOUCH_ACCEPTANCE_TIME;
         mMinimumDisplayTime = BaseHeadsUpManagerTest.TEST_MINIMUM_DISPLAY_TIME;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
index e06efe8..3d93654 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
@@ -135,7 +135,7 @@
     }
 
     @Test
-    fun streamIsMuted_getStream_volumeZero() {
+    fun streamIsMuted_getStream_volumeMin() {
         with(kosmos) {
             testScope.runTest {
                 val model by collectLastValue(underTest.getAudioStream(audioStream))
@@ -166,7 +166,7 @@
     }
 
     @Test
-    fun ringerModeVibrateAndMuted_getNotificationStream_volumeIsZero() {
+    fun ringerModeVibrateAndMuted_getNotificationStream_volumeIsMin() {
         with(kosmos) {
             testScope.runTest {
                 audioRepository.setRingerMode(RingerMode(AudioManager.RINGER_MODE_VIBRATE))
@@ -184,7 +184,7 @@
     }
 
     @Test
-    fun ringerModeVibrate_getRingerStream_volumeIsZero() {
+    fun ringerModeVibrate_getRingerStream_volumeIsMin() {
         with(kosmos) {
             testScope.runTest {
                 audioRepository.setRingerMode(RingerMode(AudioManager.RINGER_MODE_VIBRATE))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt
index 449e8bf..1ed7f5d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.volume.panel.component.spatial.domain
 
+import android.media.AudioDeviceAttributes
+import android.media.AudioDeviceInfo
 import android.media.session.MediaSession
 import android.media.session.PlaybackState
 import android.testing.TestableLooper.RunWithLooper
@@ -49,26 +51,27 @@
 class SpatialAudioAvailabilityCriteriaTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
-    private val cachedBluetoothDevice: CachedBluetoothDevice = mock {
-        whenever(address).thenReturn("test_address")
-    }
-    private val bluetoothMediaDevice: BluetoothMediaDevice = mock {
-        whenever(cachedDevice).thenReturn(cachedBluetoothDevice)
-    }
-
     private lateinit var underTest: SpatialAudioAvailabilityCriteria
 
     @Before
     fun setup() {
         with(kosmos) {
-            mediaControllerRepository.setActiveLocalMediaController(
-                mediaController.apply {
-                    whenever(packageName).thenReturn("test.pkg")
-                    whenever(sessionToken).thenReturn(MediaSession.Token(0, mock {}))
-                    whenever(playbackState).thenReturn(PlaybackState.Builder().build())
+            val cachedBluetoothDevice: CachedBluetoothDevice = mock {
+                whenever(address).thenReturn("test_address")
+            }
+            localMediaRepository.updateCurrentConnectedDevice(
+                mock<BluetoothMediaDevice> {
+                    whenever(name).thenReturn("test_device")
+                    whenever(cachedDevice).thenReturn(cachedBluetoothDevice)
                 }
             )
 
+            whenever(mediaController.packageName).thenReturn("test.pkg")
+            whenever(mediaController.sessionToken).thenReturn(MediaSession.Token(0, mock {}))
+            whenever(mediaController.playbackState).thenReturn(PlaybackState.Builder().build())
+
+            mediaControllerRepository.setActiveLocalMediaController(mediaController)
+
             underTest = SpatialAudioAvailabilityCriteria(spatialAudioComponentInteractor)
         }
     }
@@ -77,9 +80,8 @@
     fun noSpatialAudio_noHeadTracking_unavailable() {
         with(kosmos) {
             testScope.runTest {
-                localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice)
-                spatializerRepository.defaultHeadTrackingAvailable = false
-                spatializerRepository.defaultSpatialAudioAvailable = false
+                spatializerRepository.setIsSpatialAudioAvailable(headset, false)
+                spatializerRepository.setIsHeadTrackingAvailable(headset, false)
 
                 val isAvailable by collectLastValue(underTest.isAvailable())
                 runCurrent()
@@ -93,9 +95,8 @@
     fun spatialAudio_noHeadTracking_available() {
         with(kosmos) {
             testScope.runTest {
-                localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice)
-                spatializerRepository.defaultHeadTrackingAvailable = false
-                spatializerRepository.defaultSpatialAudioAvailable = true
+                spatializerRepository.setIsSpatialAudioAvailable(headset, true)
+                spatializerRepository.setIsHeadTrackingAvailable(headset, false)
 
                 val isAvailable by collectLastValue(underTest.isAvailable())
                 runCurrent()
@@ -109,9 +110,8 @@
     fun spatialAudio_headTracking_available() {
         with(kosmos) {
             testScope.runTest {
-                localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice)
-                spatializerRepository.defaultHeadTrackingAvailable = true
-                spatializerRepository.defaultSpatialAudioAvailable = true
+                spatializerRepository.setIsSpatialAudioAvailable(headset, true)
+                spatializerRepository.setIsHeadTrackingAvailable(headset, true)
 
                 val isAvailable by collectLastValue(underTest.isAvailable())
                 runCurrent()
@@ -125,8 +125,8 @@
     fun spatialAudio_headTracking_noDevice_unavailable() {
         with(kosmos) {
             testScope.runTest {
-                spatializerRepository.defaultHeadTrackingAvailable = true
-                spatializerRepository.defaultSpatialAudioAvailable = true
+                localMediaRepository.updateCurrentConnectedDevice(null)
+                spatializerRepository.setIsSpatialAudioAvailable(headset, false)
 
                 val isAvailable by collectLastValue(underTest.isAvailable())
                 runCurrent()
@@ -135,4 +135,13 @@
             }
         }
     }
+
+    private companion object {
+        val headset =
+            AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_OUTPUT,
+                AudioDeviceInfo.TYPE_BLE_HEADSET,
+                "test_address"
+            )
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
index 06ae220..281b03d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
@@ -26,6 +26,7 @@
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
 import com.android.settingslib.media.BluetoothMediaDevice
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.media.spatializerInteractor
@@ -37,6 +38,7 @@
 import com.android.systemui.volume.mediaController
 import com.android.systemui.volume.mediaControllerRepository
 import com.android.systemui.volume.mediaOutputInteractor
+import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel
 import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -74,16 +76,6 @@
 
             mediaControllerRepository.setActiveLocalMediaController(mediaController)
 
-            spatializerRepository.setIsSpatialAudioAvailable(
-                AudioDeviceAttributes(
-                    AudioDeviceAttributes.ROLE_OUTPUT,
-                    AudioDeviceInfo.TYPE_BLE_HEADSET,
-                    "test_address"
-                ),
-                true
-            )
-            spatializerRepository.defaultHeadTrackingAvailable = true
-
             underTest =
                 SpatialAudioComponentInteractor(
                     mediaOutputInteractor,
@@ -97,6 +89,7 @@
     fun setEnabled_changesIsEnabled() {
         with(kosmos) {
             testScope.runTest {
+                spatializerRepository.setIsSpatialAudioAvailable(headset, true)
                 val values by collectValues(underTest.isEnabled)
 
                 underTest.setEnabled(SpatialAudioEnabledModel.Disabled)
@@ -108,6 +101,7 @@
 
                 assertThat(values)
                     .containsExactly(
+                        SpatialAudioEnabledModel.Unknown,
                         SpatialAudioEnabledModel.Disabled,
                         SpatialAudioEnabledModel.HeadTrackingEnabled,
                         SpatialAudioEnabledModel.SpatialAudioEnabled,
@@ -116,4 +110,92 @@
             }
         }
     }
+
+    @Test
+    fun connectedDeviceSupports_isAvailable_SpatialAudio() {
+        with(kosmos) {
+            testScope.runTest {
+                spatializerRepository.setIsSpatialAudioAvailable(headset, true)
+
+                val isAvailable by collectLastValue(underTest.isAvailable)
+
+                assertThat(isAvailable)
+                    .isInstanceOf(SpatialAudioAvailabilityModel.SpatialAudio::class.java)
+            }
+        }
+    }
+
+    @Test
+    fun connectedDeviceSupportsHeadTracking_isAvailable_HeadTracking() {
+        with(kosmos) {
+            testScope.runTest {
+                spatializerRepository.setIsSpatialAudioAvailable(headset, true)
+                spatializerRepository.setIsHeadTrackingAvailable(headset, true)
+
+                val isAvailable by collectLastValue(underTest.isAvailable)
+
+                assertThat(isAvailable)
+                    .isInstanceOf(SpatialAudioAvailabilityModel.HeadTracking::class.java)
+            }
+        }
+    }
+
+    @Test
+    fun connectedDeviceDoesntSupport_isAvailable_Unavailable() {
+        with(kosmos) {
+            testScope.runTest {
+                spatializerRepository.setIsSpatialAudioAvailable(headset, false)
+
+                val isAvailable by collectLastValue(underTest.isAvailable)
+
+                assertThat(isAvailable)
+                    .isInstanceOf(SpatialAudioAvailabilityModel.Unavailable::class.java)
+            }
+        }
+    }
+
+    @Test
+    fun noConnectedDeviceBuiltinSupports_isAvailable_SpatialAudio() {
+        with(kosmos) {
+            testScope.runTest {
+                localMediaRepository.updateCurrentConnectedDevice(null)
+                spatializerRepository.setIsSpatialAudioAvailable(builtinSpeaker, true)
+
+                val isAvailable by collectLastValue(underTest.isAvailable)
+
+                assertThat(isAvailable)
+                    .isInstanceOf(SpatialAudioAvailabilityModel.SpatialAudio::class.java)
+            }
+        }
+    }
+
+    @Test
+    fun noConnectedDeviceBuiltinDoesntSupport_isAvailable_Unavailable() {
+        with(kosmos) {
+            testScope.runTest {
+                localMediaRepository.updateCurrentConnectedDevice(null)
+                spatializerRepository.setIsSpatialAudioAvailable(builtinSpeaker, false)
+
+                val isAvailable by collectLastValue(underTest.isAvailable)
+
+                assertThat(isAvailable)
+                    .isInstanceOf(SpatialAudioAvailabilityModel.Unavailable::class.java)
+            }
+        }
+    }
+
+    private companion object {
+        val headset =
+            AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_OUTPUT,
+                AudioDeviceInfo.TYPE_BLE_HEADSET,
+                "test_address"
+            )
+        val builtinSpeaker =
+            AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_OUTPUT,
+                AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
+                ""
+            )
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt
index a1e4fca..79d3fe9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt
@@ -30,28 +30,8 @@
     private val underTest = VolumeSliderInteractor()
 
     @Test
-    fun translateValueToVolume() {
-        assertThat(underTest.translateValueToVolume(30f, volumeRange)).isEqualTo(3)
-    }
-
-    @Test
-    fun processVolumeToValue_muted_zero() {
-        assertThat(underTest.processVolumeToValue(3, volumeRange, null, true)).isEqualTo(0)
-    }
-
-    @Test
-    fun processVolumeToValue_currentValue_currentValue() {
-        assertThat(underTest.processVolumeToValue(3, volumeRange, 30f, false)).isEqualTo(30f)
-    }
-
-    @Test
-    fun processVolumeToValue_currentValueDiffersVolume_returnsTranslatedVolume() {
-        assertThat(underTest.processVolumeToValue(1, volumeRange, 60f, false)).isEqualTo(10f)
-    }
-
-    @Test
-    fun processVolumeToValue_currentValueDiffersNotEnoughVolume_returnsTranslatedVolume() {
-        assertThat(underTest.processVolumeToValue(1, volumeRange, 12f, false)).isEqualTo(12f)
+    fun processVolumeToValue_returnsTranslatedVolume() {
+        assertThat(underTest.processVolumeToValue(2, volumeRange)).isEqualTo(20f)
     }
 
     private companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
index 71866b3..82ce6d7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
@@ -37,7 +37,7 @@
         DefaultComponentsLayoutManager(
             BOTTOM_BAR,
             headerComponents = listOf(COMPONENT_1),
-            footerComponents = listOf(COMPONENT_2),
+            footerComponents = listOf(COMPONENT_5, COMPONENT_2),
         )
 
     @Test
@@ -48,10 +48,18 @@
         val component2 = ComponentState(COMPONENT_2, kosmos.mockVolumePanelUiComponent, false)
         val component3 = ComponentState(COMPONENT_3, kosmos.mockVolumePanelUiComponent, false)
         val component4 = ComponentState(COMPONENT_4, kosmos.mockVolumePanelUiComponent, false)
+        val component5 = ComponentState(COMPONENT_5, kosmos.mockVolumePanelUiComponent, false)
         val layout =
             underTest.layout(
                 VolumePanelState(0, false, false),
-                setOf(bottomBarComponentState, component1, component2, component3, component4)
+                setOf(
+                    bottomBarComponentState,
+                    component1,
+                    component2,
+                    component3,
+                    component4,
+                    component5,
+                )
             )
 
         Truth.assertThat(layout.bottomBarComponent).isEqualTo(bottomBarComponentState)
@@ -59,7 +67,7 @@
             .containsExactlyElementsIn(listOf(component1))
             .inOrder()
         Truth.assertThat(layout.footerComponents)
-            .containsExactlyElementsIn(listOf(component2))
+            .containsExactlyElementsIn(listOf(component5, component2))
             .inOrder()
         Truth.assertThat(layout.contentComponents)
             .containsExactlyElementsIn(listOf(component3, component4))
@@ -85,5 +93,6 @@
         const val COMPONENT_2: VolumePanelComponentKey = "test_component:2"
         const val COMPONENT_3: VolumePanelComponentKey = "test_component:3"
         const val COMPONENT_4: VolumePanelComponentKey = "test_component:4"
+        const val COMPONENT_5: VolumePanelComponentKey = "test_component:5"
     }
 }
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml
index b562d7b..6780e57 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml
@@ -85,6 +85,7 @@
             android:layout_width="0dp"
             android:layout_height="0dp"
             android:layout_marginBottom="8dp"
+            android:focusable="false"
             androidprv:layout_constraintVertical_bias="1.0"
             androidprv:layout_constraintDimensionRatio="1.0"
             androidprv:layout_constraintStart_toStartOf="parent"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
index d9011c2..d991581 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
@@ -57,6 +57,7 @@
             android:id="@+id/lockPatternView"
             android:layout_width="0dp"
             android:layout_height="0dp"
+            android:focusable="false"
             androidprv:layout_constraintTop_toBottomOf="@id/pattern_top_guideline"
             androidprv:layout_constraintBottom_toBottomOf="parent"
             androidprv:layout_constraintLeft_toLeftOf="parent"
diff --git a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
index 5fe74aa..2d63c8d 100644
--- a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
@@ -26,7 +26,7 @@
         android:paddingHorizontal="16dp"
         android:paddingVertical="16dp"
         android:visibility="visible"
-        app:layout_constraintBottom_toTopOf="@+id/bottomGuideline"
+        app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toStartOf="@+id/rightGuideline"
         app:layout_constraintStart_toStartOf="@+id/leftGuideline"
         app:layout_constraintTop_toTopOf="@+id/topGuideline" />
@@ -59,7 +59,7 @@
         android:layout_width="0dp"
         android:layout_height="0dp"
         android:fillViewport="true"
-        android:padding="16dp"
+        android:padding="24dp"
         app:layout_constrainedHeight="true"
         app:layout_constrainedWidth="true"
         app:layout_constraintBottom_toTopOf="@+id/buttonBarrier"
@@ -82,20 +82,20 @@
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintTop_toTopOf="parent" />
 
-            <LinearLayout
-                android:id="@+id/customized_view_container"
-                android:layout_width="wrap_content"
+            <TextView
+                android:id="@+id/logo_description"
+                android:layout_width="0dp"
                 android:layout_height="wrap_content"
-                android:gravity="center_vertical"
-                android:orientation="vertical"
-                android:paddingHorizontal="0dp"
-                android:visibility="gone"
-                app:layout_constraintBottom_toBottomOf="parent"
+                android:ellipsize="marquee"
+                android:gravity="@integer/biometric_dialog_text_gravity"
+                android:marqueeRepeatLimit="1"
+                android:singleLine="true"
+                android:textAlignment="viewStart"
+                android:paddingLeft="8dp"
+                app:layout_constraintBottom_toBottomOf="@+id/logo"
                 app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintHorizontal_bias="0.0"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toBottomOf="@+id/subtitle"
-                app:layout_constraintVertical_bias="0.0" />
+                app:layout_constraintStart_toEndOf="@+id/logo"
+                app:layout_constraintTop_toTopOf="@+id/logo" />
 
             <Space
                 android:id="@+id/space_above_content"
@@ -108,6 +108,7 @@
                 style="@style/TextAppearance.AuthCredential.Title"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
+                android:layout_marginTop="12dp"
                 android:gravity="@integer/biometric_dialog_text_gravity"
                 android:paddingHorizontal="0dp"
                 android:textAlignment="viewStart"
@@ -124,6 +125,7 @@
                 style="@style/TextAppearance.AuthCredential.Subtitle"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
+                android:layout_marginTop="12dp"
                 android:gravity="@integer/biometric_dialog_text_gravity"
                 android:paddingHorizontal="0dp"
                 android:textAlignment="viewStart"
@@ -133,6 +135,21 @@
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintTop_toBottomOf="@+id/title" />
 
+            <LinearLayout
+                android:id="@+id/customized_view_container"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:gravity="center_vertical"
+                android:orientation="vertical"
+                android:paddingHorizontal="0dp"
+                android:visibility="gone"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintHorizontal_bias="0.0"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/subtitle"
+                app:layout_constraintVertical_bias="0.0" />
+
             <TextView
                 android:id="@+id/description"
                 style="@style/TextAppearance.AuthCredential.Description"
@@ -148,20 +165,6 @@
                 app:layout_constraintTop_toBottomOf="@+id/subtitle"
                 app:layout_constraintVertical_bias="0.0" />
 
-            <TextView
-                android:id="@+id/logo_description"
-                android:layout_width="0dp"
-                android:layout_height="wrap_content"
-                android:ellipsize="marquee"
-                android:gravity="@integer/biometric_dialog_text_gravity"
-                android:marqueeRepeatLimit="1"
-                android:singleLine="true"
-                android:textAlignment="viewStart"
-                android:paddingLeft="8dp"
-                app:layout_constraintBottom_toBottomOf="@+id/logo"
-                app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintStart_toEndOf="@+id/logo"
-                app:layout_constraintTop_toTopOf="@+id/logo" />
 
             <androidx.constraintlayout.widget.Barrier
                 android:id="@+id/contentBarrier"
@@ -178,7 +181,7 @@
         android:id="@+id/indicator"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginTop="16dp"
+        android:layout_marginTop="24dp"
         android:accessibilityLiveRegion="polite"
         android:fadingEdge="horizontal"
         android:gravity="center_horizontal"
@@ -192,80 +195,15 @@
         app:layout_constraintTop_toBottomOf="@+id/biometric_icon"
         app:layout_constraintVertical_bias="0.0" />
 
-    <!-- Negative Button, reserved for app -->
-    <Button
-        android:id="@+id/button_negative"
-        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical"
-        android:layout_marginBottom="8dp"
-        android:layout_marginLeft="8dp"
-        android:ellipsize="end"
-        android:maxLines="2"
-        android:visibility="invisible"
-        app:layout_constraintBottom_toBottomOf="@+id/bottomGuideline"
-        app:layout_constraintStart_toStartOf="@+id/scrollView" />
-
-    <!-- Cancel Button, replaces negative button when biometric is accepted -->
-    <Button
-        android:id="@+id/button_cancel"
-        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical"
-        android:layout_marginBottom="8dp"
-        android:layout_marginLeft="8dp"
-        android:text="@string/cancel"
-        android:visibility="invisible"
-        app:layout_constraintBottom_toBottomOf="@+id/bottomGuideline"
-        app:layout_constraintStart_toStartOf="@+id/scrollView" />
-
-    <!-- "Use Credential" Button, replaces if device credential is allowed -->
-    <Button
-        android:id="@+id/button_use_credential"
-        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical"
-        android:layout_marginBottom="8dp"
-        android:layout_marginLeft="8dp"
-        android:visibility="invisible"
-        app:layout_constraintBottom_toBottomOf="@+id/bottomGuideline"
-        app:layout_constraintStart_toStartOf="@+id/scrollView" />
-
-    <!-- Positive Button -->
-    <Button
-        android:id="@+id/button_confirm"
-        style="@*android:style/Widget.DeviceDefault.Button.Colored"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical"
-        android:layout_marginBottom="8dp"
-        android:layout_marginRight="8dp"
-        android:ellipsize="end"
-        android:maxLines="2"
-        android:text="@string/biometric_dialog_confirm"
-        android:visibility="invisible"
-        app:layout_constraintBottom_toBottomOf="@+id/bottomGuideline"
-        app:layout_constraintEnd_toEndOf="@+id/scrollView"
-        tools:visibility="invisible" />
-
-    <!-- Try Again Button -->
-    <Button
-        android:id="@+id/button_try_again"
-        style="@*android:style/Widget.DeviceDefault.Button.Colored"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical"
-        android:layout_marginBottom="8dp"
-        android:layout_marginRight="8dp"
-        android:ellipsize="end"
-        android:maxLines="2"
-        android:text="@string/biometric_dialog_try_again"
-        android:visibility="invisible"
-        app:layout_constraintBottom_toBottomOf="@+id/bottomGuideline"
-        app:layout_constraintEnd_toEndOf="@+id/scrollView" />
+    <include
+        android:id="@+id/button_bar"
+        layout="@layout/biometric_prompt_button_bar"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        app:layout_constraintBottom_toTopOf="@id/bottomGuideline"
+        app:layout_constraintEnd_toEndOf="@id/scrollView"
+        app:layout_constraintStart_toStartOf="@id/scrollView"
+        app:layout_constraintTop_toBottomOf="@id/scrollView" />
 
     <!-- Guidelines for setting panel border -->
     <androidx.constraintlayout.widget.Barrier
@@ -282,7 +220,7 @@
         android:layout_height="wrap_content"
         app:barrierAllowsGoneWidgets="false"
         app:barrierDirection="top"
-        app:constraint_referenced_ids="button_negative, button_cancel, button_use_credential, button_confirm, button_try_again" />
+        app:constraint_referenced_ids="button_bar" />
 
     <androidx.constraintlayout.widget.Guideline
         android:id="@+id/leftGuideline"
diff --git a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
index 5b30dfb..329fc46 100644
--- a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
@@ -23,92 +23,29 @@
         android:clickable="true"
         android:clipToOutline="true"
         android:importantForAccessibility="no"
-        android:paddingHorizontal="16dp"
-        android:paddingVertical="16dp"
         android:visibility="visible"
-        app:layout_constraintBottom_toTopOf="@+id/bottomGuideline"
+        app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toStartOf="@+id/rightGuideline"
         app:layout_constraintStart_toStartOf="@+id/leftGuideline"
         app:layout_constraintTop_toTopOf="@+id/topBarrier" />
 
-    <Button
-        android:id="@+id/button_negative"
-        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical"
-        android:layout_marginBottom="8dp"
-        android:layout_marginLeft="8dp"
-        android:ellipsize="end"
-        android:maxLines="2"
-        android:visibility="invisible"
-        app:layout_constraintBottom_toBottomOf="@+id/panel"
-        app:layout_constraintStart_toStartOf="@+id/panel" />
-
-    <Button
-        android:id="@+id/button_cancel"
-        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical"
-        android:layout_marginBottom="8dp"
-        android:layout_marginLeft="8dp"
-        android:text="@string/cancel"
-        android:visibility="invisible"
-        app:layout_constraintBottom_toBottomOf="@+id/panel"
-        app:layout_constraintStart_toStartOf="@+id/panel" />
-
-    <Button
-        android:id="@+id/button_use_credential"
-        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical"
-        android:layout_marginBottom="8dp"
-        android:layout_marginLeft="8dp"
-        android:visibility="invisible"
-        app:layout_constraintBottom_toBottomOf="@+id/panel"
-        app:layout_constraintStart_toStartOf="@+id/panel" />
-
-    <Button
-        android:id="@+id/button_confirm"
-        style="@*android:style/Widget.DeviceDefault.Button.Colored"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical"
-        android:layout_marginBottom="8dp"
-        android:layout_marginRight="8dp"
-        android:ellipsize="end"
-        android:maxLines="2"
-        android:text="@string/biometric_dialog_confirm"
-        android:visibility="invisible"
-        app:layout_constraintBottom_toBottomOf="@+id/panel"
-        app:layout_constraintEnd_toEndOf="@+id/panel"
-        tools:visibility="invisible" />
-
-    <Button
-        android:id="@+id/button_try_again"
-        style="@*android:style/Widget.DeviceDefault.Button.Colored"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical"
-        android:layout_marginBottom="8dp"
-        android:layout_marginRight="8dp"
-        android:ellipsize="end"
-        android:maxLines="2"
-        android:text="@string/biometric_dialog_try_again"
-        android:visibility="invisible"
-        app:layout_constraintBottom_toBottomOf="@+id/panel"
-        app:layout_constraintEnd_toEndOf="@+id/panel" />
-
-    <!-- Negative Button, reserved for app -->
+    <include
+        layout="@layout/biometric_prompt_button_bar"
+        android:id="@+id/button_bar"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        app:layout_constraintBottom_toTopOf="@id/bottomGuideline"
+        app:layout_constraintEnd_toEndOf="@id/panel"
+        app:layout_constraintStart_toStartOf="@id/panel"/>
 
     <ScrollView
         android:id="@+id/scrollView"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:fillViewport="true"
-        android:padding="16dp"
+        android:paddingBottom="36dp"
+        android:paddingHorizontal="24dp"
+        android:paddingTop="24dp"
         app:layout_constrainedHeight="true"
         app:layout_constrainedWidth="true"
         app:layout_constraintBottom_toTopOf="@+id/biometric_icon"
@@ -134,18 +71,19 @@
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintTop_toTopOf="parent" />
 
-            <LinearLayout
-                android:id="@+id/customized_view_container"
-                android:layout_width="wrap_content"
+            <TextView
+                android:id="@+id/logo_description"
+                android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:gravity="center_vertical"
-                android:orientation="vertical"
-                android:paddingHorizontal="@dimen/biometric_prompt_content_container_padding_horizontal"
-                android:visibility="gone"
-                app:layout_constraintBottom_toBottomOf="parent"
+                android:ellipsize="marquee"
+                android:gravity="@integer/biometric_dialog_text_gravity"
+                android:marqueeRepeatLimit="1"
+                android:singleLine="true"
+                android:paddingTop="16dp"
+                app:layout_constraintBottom_toTopOf="@+id/title"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toBottomOf="@+id/subtitle" />
+                app:layout_constraintTop_toBottomOf="@+id/logo" />
 
             <Space
                 android:id="@+id/space_above_content"
@@ -159,6 +97,7 @@
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:gravity="@integer/biometric_dialog_text_gravity"
+                android:paddingTop="16dp"
                 app:layout_constraintBottom_toTopOf="@+id/subtitle"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
@@ -170,23 +109,24 @@
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:gravity="@integer/biometric_dialog_text_gravity"
+                android:paddingTop="16dp"
                 app:layout_constraintBottom_toTopOf="@+id/contentBarrier"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintTop_toBottomOf="@+id/title" />
 
-            <TextView
-                android:id="@+id/logo_description"
-                android:layout_width="match_parent"
+            <LinearLayout
+                android:id="@+id/customized_view_container"
+                android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:ellipsize="marquee"
-                android:gravity="@integer/biometric_dialog_text_gravity"
-                android:marqueeRepeatLimit="1"
-                android:singleLine="true"
-                app:layout_constraintBottom_toTopOf="@+id/title"
+                android:gravity="center_vertical"
+                android:orientation="vertical"
+                android:paddingHorizontal="@dimen/biometric_prompt_content_container_padding_horizontal"
+                android:visibility="gone"
+                app:layout_constraintBottom_toBottomOf="parent"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toBottomOf="@+id/logo" />
+                app:layout_constraintTop_toBottomOf="@+id/subtitle" />
 
             <TextView
                 android:id="@+id/description"
@@ -215,7 +155,7 @@
         android:id="@+id/indicator"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginTop="16dp"
+        android:layout_marginTop="24dp"
         android:accessibilityLiveRegion="polite"
         android:fadingEdge="horizontal"
         android:gravity="center_horizontal"
@@ -247,7 +187,7 @@
         android:layout_height="wrap_content"
         app:barrierAllowsGoneWidgets="false"
         app:barrierDirection="top"
-        app:constraint_referenced_ids="button_negative, button_cancel, button_use_credential, button_confirm, button_try_again" />
+        app:constraint_referenced_ids="button_bar" />
 
     <!-- Guidelines for setting panel border -->
     <androidx.constraintlayout.widget.Guideline
diff --git a/packages/SystemUI/res/layout/app_clips_screenshot.xml b/packages/SystemUI/res/layout/app_clips_screenshot.xml
index cb638ee..bcc7bca 100644
--- a/packages/SystemUI/res/layout/app_clips_screenshot.xml
+++ b/packages/SystemUI/res/layout/app_clips_screenshot.xml
@@ -69,7 +69,7 @@
         tools:minHeight="100dp"
         tools:minWidth="100dp" />
 
-    <com.android.systemui.screenshot.CropView
+    <com.android.systemui.screenshot.scroll.CropView
         android:id="@+id/crop_view"
         android:layout_width="0px"
         android:layout_height="0px"
diff --git a/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml b/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml
new file mode 100644
index 0000000..810c7433
--- /dev/null
+++ b/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <!-- Negative Button, reserved for app -->
+    <Button
+        android:id="@+id/button_negative"
+        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginLeft="24dp"
+        android:ellipsize="end"
+        android:maxLines="2"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent" />
+
+    <!-- Cancel Button, replaces negative button when biometric is accepted -->
+    <Button
+        android:id="@+id/button_cancel"
+        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginLeft="24dp"
+        android:text="@string/cancel"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent" />
+
+    <!-- "Use Credential" Button, replaces if device credential is allowed -->
+    <Button
+        android:id="@+id/button_use_credential"
+        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginLeft="24dp"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent" />
+
+    <!-- Positive Button -->
+    <Button
+        android:id="@+id/button_confirm"
+        style="@*android:style/Widget.DeviceDefault.Button.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginRight="24dp"
+        android:ellipsize="end"
+        android:maxLines="2"
+        android:text="@string/biometric_dialog_confirm"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent" />
+
+    <!-- Try Again Button -->
+    <Button
+        android:id="@+id/button_try_again"
+        style="@*android:style/Widget.DeviceDefault.Button.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginRight="24dp"
+        android:ellipsize="end"
+        android:maxLines="2"
+        android:text="@string/biometric_dialog_try_again"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
index 74292b4..6391813 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
@@ -23,13 +23,189 @@
         android:clickable="true"
         android:clipToOutline="true"
         android:importantForAccessibility="no"
-        android:paddingHorizontal="16dp"
-        android:paddingVertical="16dp"
         android:visibility="visible"
-        app:layout_constraintBottom_toTopOf="@+id/bottomGuideline"
-        app:layout_constraintEnd_toStartOf="@+id/rightGuideline"
-        app:layout_constraintStart_toStartOf="@+id/leftGuideline"
-        app:layout_constraintTop_toTopOf="@+id/topBarrier" />
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="@id/rightGuideline"
+        app:layout_constraintStart_toStartOf="@id/leftGuideline"
+        app:layout_constraintTop_toTopOf="@id/topBarrier" />
+
+    <include
+        android:id="@+id/button_bar"
+        layout="@layout/biometric_prompt_button_bar"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        app:layout_constraintBottom_toTopOf="@id/bottomGuideline"
+        app:layout_constraintEnd_toEndOf="@id/panel"
+        app:layout_constraintStart_toStartOf="@id/panel" />
+
+    <ScrollView
+        android:id="@+id/scrollView"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fillViewport="true"
+        android:paddingBottom="36dp"
+        android:paddingHorizontal="24dp"
+        android:paddingTop="24dp"
+        app:layout_constrainedHeight="true"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintBottom_toTopOf="@+id/biometric_icon"
+        app:layout_constraintEnd_toEndOf="@id/rightGuideline"
+        app:layout_constraintStart_toStartOf="@id/leftGuideline"
+        app:layout_constraintTop_toTopOf="@+id/topGuideline"
+        app:layout_constraintVertical_bias="1.0">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/innerConstraint"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <ImageView
+                android:id="@+id/logo"
+                android:layout_width="@dimen/biometric_auth_icon_size"
+                android:layout_height="@dimen/biometric_auth_icon_size"
+                android:layout_gravity="center"
+                android:scaleType="fitXY"
+                app:layout_constraintBottom_toTopOf="@+id/logo_description"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                tools:visibility="visible" />
+
+            <TextView
+                android:id="@+id/logo_description"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="@integer/biometric_dialog_text_gravity"
+                android:singleLine="true"
+                app:layout_constraintBottom_toTopOf="@+id/title"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/logo" />
+
+            <Space
+                android:id="@+id/space_above_content"
+                android:layout_width="match_parent"
+                android:layout_height="@dimen/biometric_prompt_space_above_content"
+                android:visibility="gone" />
+
+            <TextView
+                android:id="@+id/title"
+                style="@style/TextAppearance.AuthCredential.Title"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:gravity="@integer/biometric_dialog_text_gravity"
+                android:paddingTop="16dp"
+                app:layout_constraintBottom_toTopOf="@+id/subtitle"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/logo_description" />
+
+            <TextView
+                android:id="@+id/subtitle"
+                style="@style/TextAppearance.AuthCredential.Subtitle"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:gravity="@integer/biometric_dialog_text_gravity"
+                android:paddingTop="16dp"
+                app:layout_constraintBottom_toTopOf="@+id/contentBarrier"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/title" />
+
+            <LinearLayout
+                android:id="@+id/customized_view_container"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center_vertical"
+                android:orientation="vertical"
+                android:visibility="gone"
+                android:paddingTop="8dp"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/subtitle" />
+
+            <TextView
+                android:id="@+id/description"
+                style="@style/TextAppearance.AuthCredential.Description"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:gravity="@integer/biometric_dialog_text_gravity"
+                android:paddingTop="16dp"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/subtitle" />
+
+            <androidx.constraintlayout.widget.Barrier
+                android:id="@+id/contentBarrier"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                app:barrierAllowsGoneWidgets="false"
+                app:barrierDirection="top"
+                app:constraint_referenced_ids="description, customized_view_container" />
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+    </ScrollView>
+
+    <TextView
+        android:id="@+id/indicator"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="24dp"
+        android:accessibilityLiveRegion="polite"
+        android:fadingEdge="horizontal"
+        android:gravity="center_horizontal"
+        android:scrollHorizontally="true"
+        app:layout_constraintBottom_toTopOf="@+id/buttonBarrier"
+        app:layout_constraintEnd_toEndOf="@+id/panel"
+        app:layout_constraintStart_toStartOf="@+id/panel"
+        app:layout_constraintTop_toBottomOf="@+id/biometric_icon"
+        app:layout_constraintVertical_bias="0.0" />
+
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/topBarrier"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:barrierAllowsGoneWidgets="false"
+        app:barrierDirection="top"
+        app:constraint_referenced_ids="scrollView" />
+
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/buttonBarrier"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:barrierAllowsGoneWidgets="false"
+        app:barrierDirection="top"
+        app:constraint_referenced_ids="button_bar" />
+
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/leftGuideline"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        app:layout_constraintGuide_begin="0dp" />
+
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/rightGuideline"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        app:layout_constraintGuide_end="0dp" />
+
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/bottomGuideline"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
+
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/topGuideline"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:orientation="horizontal"
+        app:layout_constraintGuide_percent="0.25" />
 
     <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
         android:id="@+id/biometric_icon"
@@ -54,249 +230,4 @@
         app:layout_constraintStart_toStartOf="@+id/biometric_icon"
         app:layout_constraintTop_toTopOf="@+id/biometric_icon" />
 
-    <ScrollView
-        android:id="@+id/scrollView"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:fillViewport="true"
-        android:padding="16dp"
-        app:layout_constrainedHeight="true"
-        app:layout_constrainedWidth="true"
-        app:layout_constraintBottom_toTopOf="@+id/biometric_icon"
-        app:layout_constraintEnd_toEndOf="@id/rightGuideline"
-        app:layout_constraintStart_toStartOf="@id/leftGuideline"
-        app:layout_constraintTop_toTopOf="@+id/topGuideline"
-        app:layout_constraintVertical_bias="1.0">
-
-        <androidx.constraintlayout.widget.ConstraintLayout
-            android:id="@+id/innerConstraint"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content">
-
-            <ImageView
-                android:id="@+id/logo"
-                android:layout_width="@dimen/biometric_auth_icon_size"
-                android:layout_height="@dimen/biometric_auth_icon_size"
-                android:layout_gravity="center"
-                android:scaleType="fitXY"
-                android:visibility="visible"
-                app:layout_constraintBottom_toTopOf="@+id/logo_description"
-                app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toTopOf="parent" />
-
-            <LinearLayout
-                android:id="@+id/customized_view_container"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:gravity="center_vertical"
-                android:orientation="vertical"
-                android:paddingHorizontal="@dimen/biometric_prompt_content_container_padding_horizontal"
-                android:visibility="gone"
-                app:layout_constraintBottom_toBottomOf="parent"
-                app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toBottomOf="@+id/subtitle" />
-
-            <Space
-                android:id="@+id/space_above_content"
-                android:layout_width="match_parent"
-                android:layout_height="@dimen/biometric_prompt_space_above_content"
-                android:visibility="gone" />
-
-            <TextView
-                android:id="@+id/title"
-                style="@style/TextAppearance.AuthCredential.Title"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:gravity="@integer/biometric_dialog_text_gravity"
-                app:layout_constraintBottom_toTopOf="@+id/subtitle"
-                app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toBottomOf="@+id/logo_description" />
-
-            <TextView
-                android:id="@+id/subtitle"
-                style="@style/TextAppearance.AuthCredential.Subtitle"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:gravity="@integer/biometric_dialog_text_gravity"
-                app:layout_constraintBottom_toTopOf="@+id/contentBarrier"
-                app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toBottomOf="@+id/title" />
-
-            <TextView
-                android:id="@+id/logo_description"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:ellipsize="marquee"
-                android:gravity="@integer/biometric_dialog_text_gravity"
-                android:marqueeRepeatLimit="1"
-                android:singleLine="true"
-                app:layout_constraintBottom_toTopOf="@+id/title"
-                app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toBottomOf="@+id/logo" />
-
-            <TextView
-                android:id="@+id/description"
-                style="@style/TextAppearance.AuthCredential.Description"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:gravity="@integer/biometric_dialog_text_gravity"
-                app:layout_constraintBottom_toBottomOf="parent"
-                app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toBottomOf="@+id/subtitle" />
-
-            <androidx.constraintlayout.widget.Barrier
-                android:id="@+id/contentBarrier"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                app:barrierAllowsGoneWidgets="false"
-                app:barrierDirection="top"
-                app:constraint_referenced_ids="description, customized_view_container" />
-
-        </androidx.constraintlayout.widget.ConstraintLayout>
-    </ScrollView>
-
-    <TextView
-        android:id="@+id/indicator"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="16dp"
-        android:accessibilityLiveRegion="polite"
-        android:fadingEdge="horizontal"
-        android:gravity="center_horizontal"
-        android:marqueeRepeatLimit="marquee_forever"
-        android:scrollHorizontally="true"
-        android:textColor="@color/biometric_dialog_gray"
-        android:textSize="12sp"
-        app:layout_constraintBottom_toTopOf="@+id/buttonBarrier"
-        app:layout_constraintEnd_toEndOf="@+id/panel"
-        app:layout_constraintStart_toStartOf="@+id/panel"
-        app:layout_constraintTop_toBottomOf="@+id/biometric_icon"
-        app:layout_constraintVertical_bias="0.0" />
-
-    <!-- Negative Button, reserved for app -->
-    <Button
-        android:id="@+id/button_negative"
-        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical"
-        android:layout_marginBottom="8dp"
-        android:layout_marginLeft="8dp"
-        android:ellipsize="end"
-        android:maxLines="2"
-        android:visibility="invisible"
-        app:layout_constraintBottom_toBottomOf="@+id/panel"
-        app:layout_constraintStart_toStartOf="@+id/panel" />
-
-    <!-- Cancel Button, replaces negative button when biometric is accepted -->
-    <Button
-        android:id="@+id/button_cancel"
-        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical"
-        android:layout_marginBottom="8dp"
-        android:layout_marginLeft="8dp"
-        android:text="@string/cancel"
-        android:visibility="invisible"
-        app:layout_constraintBottom_toBottomOf="@+id/panel"
-        app:layout_constraintStart_toStartOf="@+id/panel" />
-
-    <!-- "Use Credential" Button, replaces if device credential is allowed -->
-    <Button
-        android:id="@+id/button_use_credential"
-        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical"
-        android:layout_marginBottom="8dp"
-        android:layout_marginLeft="8dp"
-        android:visibility="invisible"
-        app:layout_constraintBottom_toBottomOf="@+id/panel"
-        app:layout_constraintStart_toStartOf="@+id/panel" />
-
-    <!-- Positive Button -->
-    <Button
-        android:id="@+id/button_confirm"
-        style="@*android:style/Widget.DeviceDefault.Button.Colored"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical"
-        android:layout_marginBottom="8dp"
-        android:layout_marginRight="8dp"
-        android:ellipsize="end"
-        android:maxLines="2"
-        android:text="@string/biometric_dialog_confirm"
-        android:visibility="invisible"
-        app:layout_constraintBottom_toBottomOf="@+id/panel"
-        app:layout_constraintEnd_toEndOf="@+id/panel"
-        tools:visibility="invisible" />
-
-    <!-- Try Again Button -->
-    <Button
-        android:id="@+id/button_try_again"
-        style="@*android:style/Widget.DeviceDefault.Button.Colored"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical"
-        android:layout_marginBottom="8dp"
-        android:layout_marginRight="8dp"
-        android:ellipsize="end"
-        android:maxLines="2"
-        android:text="@string/biometric_dialog_try_again"
-        android:visibility="invisible"
-        app:layout_constraintBottom_toBottomOf="@+id/panel"
-        app:layout_constraintEnd_toEndOf="@+id/panel" />
-
-    <!-- Guidelines for setting panel border -->
-    <androidx.constraintlayout.widget.Barrier
-        android:id="@+id/topBarrier"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        app:barrierAllowsGoneWidgets="false"
-        app:barrierDirection="top"
-        app:constraint_referenced_ids="scrollView" />
-
-    <androidx.constraintlayout.widget.Barrier
-        android:id="@+id/buttonBarrier"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        app:barrierAllowsGoneWidgets="false"
-        app:barrierDirection="top"
-        app:constraint_referenced_ids="button_negative, button_cancel, button_use_credential, button_confirm, button_try_again" />
-
-    <androidx.constraintlayout.widget.Guideline
-        android:id="@+id/leftGuideline"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="vertical"
-        app:layout_constraintGuide_begin="@dimen/biometric_dialog_border_padding" />
-
-    <androidx.constraintlayout.widget.Guideline
-        android:id="@+id/rightGuideline"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="vertical"
-        app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
-
-    <androidx.constraintlayout.widget.Guideline
-        android:id="@+id/bottomGuideline"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal"
-        app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
-
-    <androidx.constraintlayout.widget.Guideline
-        android:id="@+id/topGuideline"
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        android:orientation="horizontal"
-        app:layout_constraintGuide_percent="0.25171" />
-
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/long_screenshot.xml b/packages/SystemUI/res/layout/long_screenshot.xml
index 8a19c2e..4d207da 100644
--- a/packages/SystemUI/res/layout/long_screenshot.xml
+++ b/packages/SystemUI/res/layout/long_screenshot.xml
@@ -100,7 +100,7 @@
         app:layout_constraintStart_toStartOf="parent"
         android:transitionName="screenshot_preview_image"/>
 
-    <com.android.systemui.screenshot.CropView
+    <com.android.systemui.screenshot.scroll.CropView
         android:id="@+id/crop_view"
         android:layout_width="0px"
         android:layout_height="0px"
@@ -122,7 +122,7 @@
         tools:minHeight="100dp"
         tools:minWidth="100dp" />
 
-    <com.android.systemui.screenshot.MagnifierView
+    <com.android.systemui.screenshot.scroll.MagnifierView
         android:id="@+id/magnifier"
         android:visibility="invisible"
         android:layout_width="200dp"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index b7eff38..cac3289 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -898,8 +898,9 @@
     <dimen name="communal_tutorial_indicator_horizontal_offset">32dp</dimen>
 
     <!-- Size of the maximum radius for the enforced rounded rectangles on communal hub.
-        Keep it the same as in Launcher-->
-    <dimen name="communal_enforced_rounded_corner_max_radius">16dp</dimen>
+        Larger than the 16dp Launcher uses, to ensure consistency on the hub, where it's much more
+        obvious when corner radii differ.-->
+    <dimen name="communal_enforced_rounded_corner_max_radius">28dp</dimen>
 
     <!-- Width and height used to filter widgets displayed in the communal widget picker -->
     <dimen name="communal_widget_picker_desired_width">424dp</dimen>
@@ -1087,7 +1088,7 @@
     <dimen name="biometric_dialog_fingerprint_icon_height">80dp</dimen>
     <dimen name="biometric_dialog_button_negative_max_width">160dp</dimen>
     <dimen name="biometric_dialog_button_positive_max_width">136dp</dimen>
-    <dimen name="biometric_dialog_corner_size">4dp</dimen>
+    <dimen name="biometric_dialog_corner_size">28dp</dimen>
     <!-- Y translation when showing/dismissing the dialog-->
     <dimen name="biometric_dialog_animation_translation_offset">350dp</dimen>
     <dimen name="biometric_dialog_border_padding">4dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b8f20f6..774bbe5 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1556,11 +1556,11 @@
     <string name="volume_panel_noise_control_title">Noise Control</string>
     <!-- Label for button to enabled/disable spatial audio [CHAR_LIMIT=30] -->
     <string name="volume_panel_spatial_audio_title">Spatial Audio</string>
-    <!-- Label for button to disable spatial audio [CHAR_LIMIT=20] -->
+    <!-- Label for a spatial audio button for the case when it is disabled [CHAR_LIMIT=20] -->
     <string name="volume_panel_spatial_audio_off">Off</string>
-    <!-- Label for button to enabled spatial audio [CHAR_LIMIT=20] -->
+    <!-- Label for a spatial audio button for the case when it is enabled without head tracking [CHAR_LIMIT=20] -->
     <string name="volume_panel_spatial_audio_fixed">Fixed</string>
-    <!-- Label for button to enabled head tracking [CHAR_LIMIT=20] -->
+    <!-- Label for a spatial audio button for the case when it is enabled with head tracking [CHAR_LIMIT=20] -->
     <string name="volume_panel_spatial_audio_tracking">Head Tracking</string>
 
     <string name="volume_ringer_change">Tap to change ringer mode</string>
@@ -1576,6 +1576,18 @@
 
     <string name="volume_dialog_ringer_guidance_ring">Calls and notifications will ring (<xliff:g id="volume level" example="56">%1$s</xliff:g>)</string>
 
+    <!-- An audible a11y label for a button, that opens settings when clicked [CHAR_LIMIT=NONE] -->
+    <string name="volume_panel_enter_media_output_settings">Enter output settings</string>
+    <!-- An audible a11y state description for a button, that expands volume sliders menu [CHAR_LIMIT=NONE] -->
+    <string name="volume_panel_expanded_sliders">Volume sliders expanded</string>
+    <!-- An audible a11y state description for a button, that collapses volume sliders menu [CHAR LIMIT=NONE] -->
+    <string name="volume_panel_collapsed_sliders">Volume sliders collapsed</string>
+
+    <!-- Hint for accessibility. A stream name is a parameter. For example: double tap to mute media [CHAR_LIMIT=NONE] -->
+    <string name="volume_panel_hint_mute">mute %s</string>
+    <!-- Hint for accessibility. A stream name is a parameter. For example: double tap to unmute media [CHAR_LIMIT=NONE] -->
+    <string name="volume_panel_hint_unmute">unmute %s</string>
+
     <!-- Title with application label for media output settings. [CHAR LIMIT=20] -->
     <string name="media_output_label_title">Playing <xliff:g id="label" example="Music Player">%s</xliff:g> on</string>
 
@@ -3346,7 +3358,7 @@
     <string name="microphone_blocked_dream_overlay_content_description">Microphone blocked</string>
 
     <!-- Content description for priority mode icon on dream [CHAR LIMIT=NONE] -->
-    <string name="priority_mode_dream_overlay_content_description">Priority mode on</string>
+    <string name="priority_mode_dream_overlay_content_description">Do not disturb</string>
 
     <!-- Content description for when assistant attention is active [CHAR LIMIT=NONE] -->
     <string name="assistant_attention_content_description">User presence is detected</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index c08b083..69aa909 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -77,7 +77,7 @@
     // settings is expanded.
     public static final int SYSUI_STATE_QUICK_SETTINGS_EXPANDED = 1 << 11;
     // Winscope tracing is enabled
-    public static final int SYSUI_STATE_TRACING_ENABLED = 1 << 12;
+    public static final int SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION = 1 << 12;
     // The Assistant gesture should be constrained. It is up to the launcher implementation to
     // decide how to constrain it
     public static final int SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED = 1 << 13;
@@ -148,7 +148,7 @@
             SYSUI_STATE_OVERVIEW_DISABLED,
             SYSUI_STATE_HOME_DISABLED,
             SYSUI_STATE_SEARCH_DISABLED,
-            SYSUI_STATE_TRACING_ENABLED,
+            SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION,
             SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED,
             SYSUI_STATE_BUBBLES_EXPANDED,
             SYSUI_STATE_DIALOG_SHOWING,
@@ -211,8 +211,8 @@
         if ((flags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0) {
             str.add("a11y_long_click");
         }
-        if ((flags & SYSUI_STATE_TRACING_ENABLED) != 0) {
-            str.add("tracing");
+        if ((flags & SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION) != 0) {
+            str.add("disable_gesture_split_invocation");
         }
         if ((flags & SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED) != 0) {
             str.add("asst_gesture_constrain");
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 0bb5c17..fb331b6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -15,8 +15,10 @@
  */
 package com.android.keyguard;
 
+import android.annotation.NonNull;
 import android.app.Presentation;
 import android.content.Context;
+import android.hardware.devicestate.DeviceState;
 import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.display.DisplayManager;
 import android.media.MediaRouter;
@@ -315,11 +317,11 @@
         }
 
         @Override
-        public void onStateChanged(int state) {
+        public void onDeviceStateChanged(@NonNull DeviceState state) {
             // When concurrent state ends, the display also turns off. This is enforced in various
             // ExtensionRearDisplayPresentationTest CTS tests. So, we don't need to invoke
             // hide() since that will happen through the onDisplayRemoved callback.
-            mIsInConcurrentDisplayState = state == mConcurrentState;
+            mIsInConcurrentDisplayState = state.getIdentifier() == mConcurrentState;
         }
 
         boolean isConcurrentDisplayActive(Display display) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index c0ae4a1..7f9ae5e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -250,6 +250,10 @@
     @Override
     protected void onViewAttached() {
         mStatusArea = mView.findViewById(R.id.keyguard_status_area);
+        if (migrateClocksToBlueprint()) {
+            return;
+        }
+
         mStatusArea.addOnLayoutChangeListener(mStatusAreaLayoutChangeListener);
         mKeyguardUpdateMonitor.registerCallback(mInfoCallback);
         mConfigurationController.addCallback(mConfigurationListener);
@@ -257,6 +261,10 @@
 
     @Override
     protected void onViewDetached() {
+        if (migrateClocksToBlueprint()) {
+            return;
+        }
+
         mStatusArea.removeOnLayoutChangeListener(mStatusAreaLayoutChangeListener);
         mKeyguardUpdateMonitor.removeCallback(mInfoCallback);
         mConfigurationController.removeCallback(mConfigurationListener);
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 8e98150..039a2e5 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -23,6 +23,7 @@
 import static com.android.keyguard.LockIconView.ICON_LOCK;
 import static com.android.keyguard.LockIconView.ICON_UNLOCK;
 import static com.android.systemui.Flags.keyguardBottomAreaRefactor;
+import static com.android.systemui.Flags.migrateClocksToBlueprint;
 import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
 import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1;
 import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
@@ -452,7 +453,7 @@
     private void updateLockIconLocation() {
         final float scaleFactor = mAuthController.getScaleFactor();
         final int scaledPadding = (int) (mDefaultPaddingPx * scaleFactor);
-        if (keyguardBottomAreaRefactor()) {
+        if (keyguardBottomAreaRefactor() || migrateClocksToBlueprint()) {
             mView.getLockIcon().setPadding(scaledPadding, scaledPadding, scaledPadding,
                     scaledPadding);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
index 0ef3d20..a90d4b2 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
@@ -118,6 +118,12 @@
                 iconResId = R.drawable.pip_ic_close_white
             )
         )
+
+        // Ensure this is unfocusable & uninteractable
+        isClickable = false
+        isFocusable = false
+        importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO
+
         // END DragToInteractView modification
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
index d3e85e0..1f04599 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -113,13 +113,8 @@
 
     /* Moves position without updating underlying percentage position. Can be animated. */
     void moveToPosition(PointF position, boolean animateMovement) {
-        if (Flags.floatingMenuImeDisplacementAnimation()) {
-            moveToPositionX(position.x, animateMovement);
-            moveToPositionY(position.y, animateMovement);
-        } else {
-            moveToPositionX(position.x, /* animateMovement = */ false);
-            moveToPositionY(position.y, /* animateMovement = */ false);
-        }
+        moveToPositionX(position.x, animateMovement);
+        moveToPositionY(position.y, animateMovement);
     }
 
     void moveToPositionX(float positionX) {
@@ -127,7 +122,7 @@
     }
 
     void moveToPositionX(float positionX, boolean animateMovement) {
-        if (animateMovement && Flags.floatingMenuImeDisplacementAnimation()) {
+        if (animateMovement) {
             springMenuWith(DynamicAnimation.TRANSLATION_X,
                     createSpringForce(),
                     /* velocity = */ 0,
@@ -142,7 +137,7 @@
     }
 
     void moveToPositionY(float positionY, boolean animateMovement) {
-        if (animateMovement && Flags.floatingMenuImeDisplacementAnimation()) {
+        if (animateMovement) {
             springMenuWith(DynamicAnimation.TRANSLATION_Y,
                     createSpringForce(),
                     /* velocity = */ 0,
@@ -455,7 +450,7 @@
                 ? MIN_PERCENT
                 : Math.min(MAX_PERCENT, position.y / draggableBounds.height());
 
-        if (Flags.floatingMenuImeDisplacementAnimation() && !writeToPosition) {
+        if (!writeToPosition) {
             mMenuView.onEdgeChangedIfNeeded();
         } else {
             mMenuView.persistPositionAndUpdateEdge(new Position(percentageX, percentageY));
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
index e57323b..35fe6b1 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
@@ -74,6 +74,12 @@
         addView(mTextView, Index.TEXT_VIEW,
                 new LayoutParams(/* width= */ 0, WRAP_CONTENT, /* weight= */ 1));
         addView(mUndoButton, Index.UNDO_BUTTON, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+
+        // The message box is not focusable, but will announce its contents when it appears.
+        // The textView and button are still interactable.
+        setClickable(false);
+        setFocusable(false);
+        setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
index 577bbc0..0c9712d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
@@ -21,17 +21,10 @@
 import android.annotation.SuppressLint;
 import android.content.ComponentCallbacks;
 import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.drawable.GradientDrawable;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.provider.SettingsStringUtil;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 import android.widget.FrameLayout;
@@ -101,6 +94,10 @@
         loadLayoutResources();
 
         addView(mTargetFeaturesView);
+
+        setClickable(false);
+        setFocusable(false);
+        setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
     }
 
     @Override
@@ -224,8 +221,7 @@
         }
 
         // We can skip animating if FAB is not visible
-        if (Flags.floatingMenuImeDisplacementAnimation()
-                && animateMovement && getVisibility() == VISIBLE) {
+        if (animateMovement && getVisibility() == VISIBLE) {
             mMenuAnimationController.moveToPosition(position, /* animateMovement = */ true);
             // onArrivalAtPosition() is called at the end of the animation.
         } else {
@@ -331,7 +327,7 @@
             mMoveToTuckedListener.onMoveToTuckedChanged(isMoveToTucked);
         }
 
-        if (Flags.floatingMenuOverlapsNavBarsFlag() && !Flags.floatingMenuAnimatedTuck()) {
+        if (!Flags.floatingMenuAnimatedTuck()) {
             if (isMoveToTucked) {
                 final float halfWidth = getMenuWidth() / 2.0f;
                 final boolean isOnLeftSide = mMenuAnimationController.isOnLeftSide();
@@ -431,22 +427,6 @@
         onPositionChanged();
     }
 
-    void gotoEditScreen() {
-        if (!Flags.floatingMenuDragToEdit()) {
-            return;
-        }
-        mMenuAnimationController.flingMenuThenSpringToEdge(
-                getMenuPosition().x, 100f, 0f);
-
-        Intent intent = getIntentForEditScreen();
-        PackageManager packageManager = getContext().getPackageManager();
-        List<ResolveInfo> activities = packageManager.queryIntentActivities(intent,
-                PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY));
-        if (!activities.isEmpty()) {
-            mContext.startActivity(intent);
-        }
-    }
-
     void incrementTexMetricForAllTargets(String metric) {
         if (!Flags.floatingMenuDragToEdit()) {
             return;
@@ -461,23 +441,6 @@
         Counter.logIncrementWithUid(metric, uid);
     }
 
-    Intent getIntentForEditScreen() {
-        List<String> targets = new SettingsStringUtil.ColonDelimitedSet.OfStrings(
-                mSecureSettings.getStringForUser(
-                        Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
-                        UserHandle.USER_CURRENT)).stream().toList();
-
-        Intent intent = new Intent(
-                Settings.ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS);
-        Bundle args = new Bundle();
-        Bundle fragmentArgs = new Bundle();
-        fragmentArgs.putStringArray("targets", targets.toArray(new String[0]));
-        args.putBundle(":settings:show_fragment_args", fragmentArgs);
-        intent.replaceExtras(args);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-        return intent;
-    }
-
     private InstantInsetLayerDrawable getContainerViewInsetLayer() {
         return (InstantInsetLayerDrawable) getBackground();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
index 4865fce..760e1c3 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
@@ -34,7 +34,6 @@
 
 import androidx.annotation.DimenRes;
 
-import com.android.systemui.Flags;
 import com.android.systemui.res.R;
 
 import java.lang.annotation.Retention;
@@ -155,11 +154,6 @@
         final int margin = getMenuMargin();
         final Rect draggableBounds = new Rect(getWindowAvailableBounds());
 
-        if (!Flags.floatingMenuOverlapsNavBarsFlag()) {
-            // Initializes start position for mapping the translation of the menu view.
-            draggableBounds.offsetTo(/* newLeft= */ 0, /* newTop= */ 0);
-        }
-
         draggableBounds.top += margin;
         draggableBounds.right -= getMenuWidth();
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index cd3b8a6..85bf784 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -36,19 +36,24 @@
 import android.annotation.StringDef;
 import android.annotation.SuppressLint;
 import android.app.NotificationManager;
+import android.app.StatusBarManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentCallbacks;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Rect;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.provider.SettingsStringUtil;
 import android.util.ArraySet;
 import android.view.MotionEvent;
 import android.view.View;
@@ -120,6 +125,7 @@
     private final MenuAnimationController mMenuAnimationController;
     private final AccessibilityManager mAccessibilityManager;
     private final NotificationManager mNotificationManager;
+    private StatusBarManager mStatusBarManager;
     private final MenuNotificationFactory mNotificationFactory;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final IAccessibilityFloatingMenu mFloatingMenu;
@@ -246,6 +252,7 @@
         mDismissView.getCircle().setId(R.id.action_remove_menu);
         mNotificationFactory = new MenuNotificationFactory(context);
         mNotificationManager = context.getSystemService(NotificationManager.class);
+        mStatusBarManager = context.getSystemService(StatusBarManager.class);
 
         if (Flags.floatingMenuDragToEdit()) {
             mDragToInteractAnimationController = new DragToInteractAnimationController(
@@ -319,6 +326,9 @@
         if (Flags.floatingMenuAnimatedTuck()) {
             setClipChildren(true);
         }
+        setClickable(false);
+        setFocusable(false);
+        setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
     }
 
     @Override
@@ -443,21 +453,18 @@
     }
 
     public void onMoveToTuckedChanged(boolean moveToTuck) {
-        if (Flags.floatingMenuOverlapsNavBarsFlag()) {
-            if (moveToTuck) {
-                final Rect bounds = mMenuViewAppearance.getWindowAvailableBounds();
-                final int[] location = getLocationOnScreen();
-                bounds.offset(
-                        location[0],
-                        location[1]
-                );
+        if (moveToTuck) {
+            final Rect bounds = mMenuViewAppearance.getWindowAvailableBounds();
+            final int[] location = getLocationOnScreen();
+            bounds.offset(
+                    location[0],
+                    location[1]
+            );
 
-                setClipBounds(bounds);
-            }
-            // Instead of clearing clip bounds when moveToTuck is false,
-            // wait until the spring animation finishes.
+            setClipBounds(bounds);
         }
-        // Function is a no-operation if flag is disabled.
+        // Instead of clearing clip bounds when moveToTuck is false,
+        // wait until the spring animation finishes.
     }
 
     private void onSpringAnimationsEndAction() {
@@ -475,9 +482,7 @@
                 setClipBounds(null);
             }
         }
-        if (Flags.floatingMenuImeDisplacementAnimation()) {
-            mMenuView.onArrivalAtPosition(false);
-        }
+        mMenuView.onArrivalAtPosition(false);
     }
 
     void dispatchAccessibilityAction(int id) {
@@ -490,7 +495,7 @@
             mMenuView.incrementTexMetricForAllTargets(TEX_METRIC_DISMISS);
         } else if (id == R.id.action_edit
                 && Flags.floatingMenuDragToEdit()) {
-            mMenuView.gotoEditScreen();
+            gotoEditScreen();
             mMenuView.incrementTexMetricForAllTargets(TEX_METRIC_EDIT);
         }
         mDismissView.hide();
@@ -499,6 +504,40 @@
                 id, /* scaleUp= */ false);
     }
 
+    void gotoEditScreen() {
+        if (!Flags.floatingMenuDragToEdit()) {
+            return;
+        }
+        mMenuAnimationController.flingMenuThenSpringToEdge(
+                mMenuView.getMenuPosition().x, 100f, 0f);
+
+        Intent intent = getIntentForEditScreen();
+        PackageManager packageManager = getContext().getPackageManager();
+        List<ResolveInfo> activities = packageManager.queryIntentActivities(intent,
+                PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY));
+        if (!activities.isEmpty()) {
+            mContext.startActivity(intent);
+            mStatusBarManager.collapsePanels();
+        }
+    }
+
+    Intent getIntentForEditScreen() {
+        List<String> targets = new SettingsStringUtil.ColonDelimitedSet.OfStrings(
+                mSecureSettings.getStringForUser(
+                        Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
+                        UserHandle.USER_CURRENT)).stream().toList();
+
+        Intent intent = new Intent(
+                Settings.ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS);
+        Bundle args = new Bundle();
+        Bundle fragmentArgs = new Bundle();
+        fragmentArgs.putStringArray("targets", targets.toArray(new String[0]));
+        args.putBundle(":settings:show_fragment_args", fragmentArgs);
+        intent.replaceExtras(args);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        return intent;
+    }
+
     private CharSequence getMigrationMessage() {
         final Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
index bc9d1ff..6b1240b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
@@ -20,11 +20,9 @@
 
 import android.content.Context;
 import android.graphics.PixelFormat;
-import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
 
-import com.android.systemui.Flags;
 import com.android.systemui.util.settings.SecureSettings;
 
 /**
@@ -88,14 +86,9 @@
         params.privateFlags |= PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
         params.windowAnimations = android.R.style.Animation_Translucent;
         // Insets are configured to allow the menu to display over navigation and system bars.
-        if (Flags.floatingMenuOverlapsNavBarsFlag()) {
-            params.setFitInsetsTypes(0);
-            params.layoutInDisplayCutoutMode =
-                    WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-        } else {
-            params.setFitInsetsTypes(
-                    WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout());
-        }
+        params.setFitInsetsTypes(0);
+        params.layoutInDisplayCutoutMode =
+                WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         return params;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 9de71c1..99cdc01 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -340,13 +340,19 @@
         mPanelInteractionDetector = panelInteractionDetector;
         mApplicationCoroutineScope = applicationCoroutineScope;
 
+        mPromptViewModel = promptViewModel;
         mTranslationY = getResources()
                 .getDimension(R.dimen.biometric_dialog_animation_translation_offset);
         mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN;
         mBiometricCallback = new BiometricCallback();
 
+        mPromptSelectorInteractorProvider = promptSelectorInteractorProvider;
+        mPromptSelectorInteractorProvider.get().setShouldShowBpWithoutIconForCredential(
+                config.mPromptInfo);
+
         final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
-        if (constraintBp()) {
+        if (constraintBp() && (Utils.isBiometricAllowed(config.mPromptInfo)
+                || mPromptViewModel.getShowBpWithoutIconForCredential().getValue())) {
             mLayout = (ConstraintLayout) layoutInflater.inflate(
                     R.layout.biometric_prompt_constraint_layout, this, false /* attachToRoot */);
         } else {
@@ -375,9 +381,7 @@
         mBackgroundExecutor = bgExecutor;
         mInteractionJankMonitor = jankMonitor;
         mPromptCredentialInteractor = credentialInteractor;
-        mPromptSelectorInteractorProvider = promptSelectorInteractorProvider;
         mCredentialViewModelProvider = credentialViewModelProvider;
-        mPromptViewModel = promptViewModel;
         mFpProps = fpProps;
         mFaceProps = faceProps;
 
@@ -408,10 +412,6 @@
             @Nullable FaceSensorPropertiesInternal faceProps,
             @NonNull VibratorHelper vibratorHelper
     ) {
-        // Set this value before showing either of the prompt.
-        mPromptSelectorInteractorProvider.get().setShouldShowBpWithoutIconForCredential(
-                config.mPromptInfo);
-
         if (Utils.isBiometricAllowed(config.mPromptInfo)
                 || mPromptViewModel.getShowBpWithoutIconForCredential().getValue()) {
             addBiometricView(config, layoutInflater, viewModel, fpProps, faceProps, vibratorHelper);
@@ -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/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index 478ef8f..1dfd2e5f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -23,6 +23,7 @@
 import android.graphics.Rect
 import android.transition.AutoTransition
 import android.transition.TransitionManager
+import android.util.TypedValue
 import android.view.Surface
 import android.view.View
 import android.view.ViewGroup
@@ -54,13 +55,10 @@
 import com.android.systemui.biometrics.ui.viewmodel.isRight
 import com.android.systemui.biometrics.ui.viewmodel.isSmall
 import com.android.systemui.biometrics.ui.viewmodel.isTop
-import com.android.systemui.keyguard.ui.view.layout.sections.setVisibility
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.res.R
-import kotlin.math.abs
 import kotlin.math.min
 import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.launch
 
 /** Helper for [BiometricViewBinder] to handle resize transitions. */
@@ -106,6 +104,13 @@
             val iconHolderView = view.requireViewById<View>(R.id.biometric_icon)
             val panelView = view.requireViewById<View>(R.id.panel)
             val cornerRadius = view.resources.getDimension(R.dimen.biometric_dialog_corner_size)
+            val cornerRadiusPx =
+                TypedValue.applyDimension(
+                        TypedValue.COMPLEX_UNIT_DIP,
+                        cornerRadius,
+                        view.resources.displayMetrics
+                    )
+                    .toInt()
 
             // ConstraintSets for animating between prompt sizes
             val mediumConstraintSet = ConstraintSet()
@@ -116,9 +121,10 @@
 
             val largeConstraintSet = ConstraintSet()
             largeConstraintSet.clone(mediumConstraintSet)
-            largeConstraintSet.setGuidelineBegin(leftGuideline.id, 0)
-            largeConstraintSet.setGuidelineEnd(rightGuideline.id, 0)
-            largeConstraintSet.setGuidelineEnd(bottomGuideline.id, 0)
+            largeConstraintSet.setVisibility(iconHolderView.id, View.GONE)
+            largeConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE)
+            largeConstraintSet.setVisibility(R.id.indicator, View.GONE)
+            largeConstraintSet.setVisibility(R.id.scrollView, View.GONE)
 
             // TODO: Investigate better way to handle 180 rotations
             val flipConstraintSet = ConstraintSet()
@@ -141,7 +147,13 @@
             panelView.outlineProvider =
                 object : ViewOutlineProvider() {
                     override fun getOutline(view: View, outline: Outline) {
-                        outline.setRoundRect(0, 0, view.width, view.height, cornerRadius)
+                        outline.setRoundRect(
+                            0,
+                            0,
+                            view.width,
+                            view.height + cornerRadiusPx,
+                            cornerRadiusPx.toFloat()
+                        )
                     }
                 }
 
@@ -164,35 +176,81 @@
 
                     when {
                         position.isTop -> {
+                            // Round bottom corners
+                            panelView.outlineProvider =
+                                object : ViewOutlineProvider() {
+                                    override fun getOutline(view: View, outline: Outline) {
+                                        outline.setRoundRect(
+                                            0,
+                                            -cornerRadiusPx,
+                                            view.width,
+                                            view.height,
+                                            cornerRadiusPx.toFloat()
+                                        )
+                                    }
+                                }
                             left = windowBounds.centerX() - width / 2 + viewModel.promptMargin
                             right = windowBounds.centerX() - width / 2 + viewModel.promptMargin
                             bottom = iconHolderView.centerY() * 2 - iconHolderView.centerY() / 4
                         }
                         position.isBottom -> {
-                            left = windowBounds.centerX() - width / 2 + viewModel.promptMargin
-                            right = windowBounds.centerX() - width / 2 + viewModel.promptMargin
-                            bottom = viewModel.promptMargin
+                            // Round top corners
+                            panelView.outlineProvider =
+                                object : ViewOutlineProvider() {
+                                    override fun getOutline(view: View, outline: Outline) {
+                                        outline.setRoundRect(
+                                            0,
+                                            0,
+                                            view.width,
+                                            view.height + cornerRadiusPx,
+                                            cornerRadiusPx.toFloat()
+                                        )
+                                    }
+                                }
+
+                            left = windowBounds.centerX() - width / 2
+                            right = windowBounds.centerX() - width / 2
+                            bottom = if (view.isLandscape()) bottomInset else 0
                         }
                         position.isLeft -> {
-                            left = viewModel.promptMargin
-                            mid =
-                                abs(
-                                    windowBounds.width() - iconHolderView.centerX() * 2 +
-                                        viewModel.promptMargin
-                                )
+                            // Round right corners
+                            panelView.outlineProvider =
+                                object : ViewOutlineProvider() {
+                                    override fun getOutline(view: View, outline: Outline) {
+                                        outline.setRoundRect(
+                                            -cornerRadiusPx,
+                                            0,
+                                            view.width,
+                                            view.height,
+                                            cornerRadiusPx.toFloat()
+                                        )
+                                    }
+                                }
+
+                            left = 0
+                            mid = (windowBounds.width() * .85).toInt() / 2
                             right = windowBounds.width() - (windowBounds.width() * .85).toInt()
-                            bottom = bottomInset + viewModel.promptMargin
+                            bottom = if (view.isLandscape()) bottomInset else 0
                         }
                         position.isRight -> {
+                            // Round left corners
+                            panelView.outlineProvider =
+                                object : ViewOutlineProvider() {
+                                    override fun getOutline(view: View, outline: Outline) {
+                                        outline.setRoundRect(
+                                            0,
+                                            0,
+                                            view.width + cornerRadiusPx,
+                                            view.height,
+                                            cornerRadiusPx.toFloat()
+                                        )
+                                    }
+                                }
+
                             left = windowBounds.width() - (windowBounds.width() * .85).toInt()
-                            right = viewModel.promptMargin
-                            bottom = bottomInset + viewModel.promptMargin
-                            mid =
-                                abs(
-                                    iconHolderView.centerX() -
-                                        (windowBounds.width() - iconHolderView.centerX()) -
-                                        viewModel.promptMargin
-                                )
+                            right = 0
+                            bottom = if (view.isLandscape()) bottomInset else 0
+                            mid = windowBounds.width() - (windowBounds.width() * .85).toInt() / 2
                         }
                     }
 
@@ -226,16 +284,8 @@
                     }
                 }
 
-                fun setConstraintSetVisibility() {
-                    viewsToHideWhenSmall.forEach {
-                        mediumConstraintSet.setVisibility(it.id, it.showContentOrHide())
-                        largeConstraintSet.setVisibility(it.id, View.GONE)
-                        smallConstraintSet.setVisibility(it.id, View.GONE)
-                    }
-
-                    largeConstraintSet.setVisibility(iconHolderView.id, View.GONE)
-                    largeConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE)
-                    largeConstraintSet.setVisibility(R.id.indicator, View.GONE)
+                fun setVisibilities(size: PromptSize) {
+                    viewsToHideWhenSmall.forEach { it.showContentOrHide(forceHide = size.isSmall) }
 
                     if (viewModel.showBpWithoutIconForCredential.value) {
                         smallConstraintSet.setVisibility(iconHolderView.id, View.GONE)
@@ -261,7 +311,7 @@
                                 }
 
                                 measureBounds(position)
-                                setConstraintSetVisibility()
+                                setVisibilities(size)
                                 when {
                                     size.isSmall -> {
                                         val ratio =
@@ -353,7 +403,7 @@
 
                             // prepare for animated size transitions
                             for (v in viewsToHideWhenSmall) {
-                                v.visibility = v.showContentOrHide(forceHide = size.isSmall)
+                                v.showContentOrHide(forceHide = size.isSmall)
                             }
 
                             if (viewModel.showBpWithoutIconForCredential.value) {
@@ -490,14 +540,15 @@
     }
 }
 
-private fun View.showContentOrHide(forceHide: Boolean = false): Int {
+private fun View.showContentOrHide(forceHide: Boolean = false) {
     val isTextViewWithBlankText = this is TextView && this.text.isBlank()
     val isImageViewWithoutImage = this is ImageView && this.drawable == null
-    return if (forceHide || isTextViewWithBlankText || isImageViewWithoutImage) {
-        View.GONE
-    } else {
-        View.VISIBLE
-    }
+    visibility =
+        if (forceHide || isTextViewWithBlankText || isImageViewWithoutImage) {
+            View.GONE
+        } else {
+            View.VISIBLE
+        }
 }
 
 private fun View.centerX(): Int {
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..d8265c7 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
@@ -63,12 +63,6 @@
 
                     iconOverlayView.layoutParams.width = iconViewLayoutParamSizeOverride.first
                     iconOverlayView.layoutParams.height = iconViewLayoutParamSizeOverride.second
-                } else {
-                    iconView.layoutParams.width = viewModel.fingerprintIconWidth.first()
-                    iconView.layoutParams.height = viewModel.fingerprintIconWidth.first()
-
-                    iconOverlayView.layoutParams.width = viewModel.fingerprintIconWidth.first()
-                    iconOverlayView.layoutParams.height = viewModel.fingerprintIconWidth.first()
                 }
 
                 var faceIcon: AnimatedVectorDrawable? = null
@@ -84,9 +78,12 @@
                     }
 
                 launch {
-                    var width = 0
-                    var height = 0
-                    viewModel.activeAuthType.collect { activeAuthType ->
+                    combine(viewModel.activeAuthType, viewModel.iconSize, ::Pair).collect {
+                        (activeAuthType, iconSize) ->
+                        // 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 -> {
@@ -104,8 +101,6 @@
                                 }
                             }
                             AuthType.Face -> {
-                                width = viewModel.faceIconWidth
-                                height = viewModel.faceIconHeight
                                 /**
                                  * Set to true by default since face icon is a drawable, which
                                  * doesn't have a LottieOnCompositionLoadedListener equivalent.
@@ -117,11 +112,12 @@
                             }
                         }
 
-                        if (width != 0 && height != 0) {
-                            iconView.layoutParams.width = width
-                            iconView.layoutParams.height = height
-                            iconOverlayView.layoutParams.width = width
-                            iconOverlayView.layoutParams.height = height
+                        if (iconViewLayoutParamSizeOverride == null) {
+                            iconView.layoutParams.width = iconSize.first
+                            iconView.layoutParams.height = iconSize.second
+
+                            iconOverlayView.layoutParams.width = iconSize.first
+                            iconOverlayView.layoutParams.height = iconSize.second
                         }
                     }
                 }
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/PromptIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
index 257eb4a..d0c140b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
@@ -21,6 +21,7 @@
 import android.annotation.RawRes
 import android.content.res.Configuration
 import android.graphics.Rect
+import android.hardware.face.Face
 import android.util.RotationUtils
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
@@ -65,9 +66,10 @@
      */
     val activeAuthType: Flow<AuthType> =
         combine(
+            promptViewModel.size,
             promptViewModel.modalities.distinctUntilChanged(),
             promptViewModel.faceMode.distinctUntilChanged()
-        ) { modalities, faceMode ->
+        ) { _, modalities, faceMode ->
             if (modalities.hasFaceAndFingerprint && !faceMode) {
                 AuthType.Coex
             } else if (modalities.hasFaceOnly || faceMode) {
@@ -158,13 +160,18 @@
     /** Tracks whether a face iconView last pulsed light to dark (vs. dark to light) */
     val lastPulseLightToDark: Flow<Boolean> = _lastPulseLightToDark.asStateFlow()
 
-    /** Layout params for fingerprint iconView */
-    val fingerprintIconWidth: Flow<Int> = promptViewModel.fingerprintSensorDiameter
-    val fingerprintIconHeight: Flow<Int> = promptViewModel.fingerprintSensorDiameter
-
-    /** Layout params for face iconView */
-    val faceIconWidth: Int = promptViewModel.faceIconWidth
-    val faceIconHeight: Int = promptViewModel.faceIconHeight
+    val iconSize: Flow<Pair<Int, Int>> =
+        combine(
+            activeAuthType,
+            promptViewModel.fingerprintSensorWidth,
+            promptViewModel.fingerprintSensorHeight,
+        ) { activeAuthType, fingerprintSensorWidth, fingerprintSensorHeight ->
+            if (activeAuthType == AuthType.Face) {
+                Pair(promptViewModel.faceIconWidth, promptViewModel.faceIconHeight)
+            } else {
+                Pair(fingerprintSensorWidth, fingerprintSensorHeight)
+            }
+        }
 
     /** Current BiometricPromptLayout.iconView asset. */
     val iconAsset: Flow<Int> =
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..fbd87fd 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
@@ -85,7 +86,7 @@
     val faceIconHeight: Int =
         context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
 
-    val fingerprintSensorDiameter: Flow<Int> =
+    val fingerprintSensorWidth: Flow<Int> =
         combine(modalities, udfpsOverlayInteractor.udfpsOverlayParams) { modalities, overlayParams
             ->
             if (modalities.hasUdfps) {
@@ -95,6 +96,16 @@
             }
         }
 
+    val fingerprintSensorHeight: Flow<Int> =
+        combine(modalities, udfpsOverlayInteractor.udfpsOverlayParams) { modalities, overlayParams
+            ->
+            if (modalities.hasUdfps) {
+                overlayParams.sensorBounds.height()
+            } else {
+                fingerprintIconHeight
+            }
+        }
+
     private val _accessibilityHint = MutableSharedFlow<String>()
 
     /** Hint for talkback directional guidance */
@@ -217,18 +228,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 +253,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 +286,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 +306,7 @@
         promptSelectorInteractor.prompt
             .map {
                 when {
-                    !customBiometricPrompt() || it == null -> ""
+                    !(customBiometricPrompt() && constraintBp()) || it == null -> ""
                     it.logoDescription != null -> it.logoDescription
                     else ->
                         try {
@@ -329,7 +331,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 =
@@ -350,7 +352,7 @@
             position,
             message,
         ) { size, _, message ->
-            size.isNotSmall && message.message.isNotBlank()
+            size.isMedium && message.message.isNotBlank()
         }
 
     /** If the auth is pending confirmation and the confirm button should be shown. */
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
index af32eb5..000f03a 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
@@ -109,7 +109,8 @@
             biometricSettingsRepository.isFingerprintAuthCurrentlyAllowed.value &&
             !keyguardUpdateMonitor.isFingerprintLockedOut &&
             !keyguardStateController.isUnlocked &&
-            !statusBarStateController.isDozing
+            !statusBarStateController.isDozing &&
+            !bouncerRepository.primaryBouncerShow.value
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
index 8c87b0c..893887f 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
@@ -93,6 +93,8 @@
     val keyguardAuthenticatedPrimaryAuth: Flow<Int> = repository.keyguardAuthenticatedPrimaryAuth
     val keyguardAuthenticatedBiometrics: Flow<Boolean> =
         repository.keyguardAuthenticatedBiometrics.filterNotNull()
+    val keyguardAuthenticatedBiometricsHandled: Flow<Unit> =
+        repository.keyguardAuthenticatedBiometrics.filter { it == null }.map {}
     val userRequestedBouncerWhenAlreadyAuthenticated: Flow<Int> =
         repository.userRequestedBouncerWhenAlreadyAuthenticated.filterNotNull()
     val isShowing: StateFlow<Boolean> = repository.primaryBouncerShow
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt
index 1c9d1f0..e1fea5f 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -23,12 +23,14 @@
 import com.android.systemui.bouncer.ui.BouncerView
 import com.android.systemui.bouncer.ui.BouncerViewDelegate
 import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
 
 /** Models UI state for the lock screen bouncer; handles user input. */
+@ExperimentalCoroutinesApi
 class KeyguardBouncerViewModel
 @Inject
 constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/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..ef686f9 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.communal
 
+import android.provider.Settings
 import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.CoreStartable
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
@@ -24,25 +25,32 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dock.DockManager
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.util.kotlin.emitOnStart
+import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import com.android.systemui.util.settings.SystemSettings
 import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
 import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.FlowPreview
 import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
 
 /**
  * A [CoreStartable] responsible for automatically navigating between communal scenes when certain
  * conditions are met.
  */
-@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class CommunalSceneStartable
 @Inject
@@ -50,9 +58,13 @@
     private val dockManager: DockManager,
     private val communalInteractor: CommunalInteractor,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    private val keyguardInteractor: KeyguardInteractor,
+    private val systemSettings: SystemSettings,
     @Application private val applicationScope: CoroutineScope,
     @Background private val bgScope: CoroutineScope,
 ) : CoreStartable {
+    private var screenTimeout: Int = DEFAULT_SCREEN_TIMEOUT
+
     override fun start() {
         // Handle automatically switching based on keyguard state.
         keyguardTransitionInteractor.startedKeyguardTransitionStep
@@ -78,6 +90,49 @@
         //                }
         //            }
         //            .launchIn(bgScope)
+
+        systemSettings
+            .observerFlow(Settings.System.SCREEN_OFF_TIMEOUT)
+            // Read the setting value on start.
+            .emitOnStart()
+            .onEach {
+                screenTimeout =
+                    systemSettings.getInt(
+                        Settings.System.SCREEN_OFF_TIMEOUT,
+                        DEFAULT_SCREEN_TIMEOUT
+                    )
+            }
+            .launchIn(bgScope)
+
+        // Handle timing out back to the dream.
+        bgScope.launch {
+            combine(
+                    communalInteractor.desiredScene,
+                    // Emit a value on start so the combine starts.
+                    communalInteractor.userActivity.emitOnStart()
+                ) { scene, _ ->
+                    // Time out should run whenever we're dreaming and the hub is open, even if not
+                    // docked.
+                    scene == CommunalScenes.Communal
+                }
+                // mapLatest cancels the previous action block when new values arrive, so any
+                // already running timeout gets cancelled when conditions change or user interaction
+                // is detected.
+                .mapLatest { shouldTimeout ->
+                    if (!shouldTimeout) {
+                        return@mapLatest false
+                    }
+
+                    delay(screenTimeout.milliseconds)
+                    true
+                }
+                .sample(keyguardInteractor.isDreaming, ::Pair)
+                .collect { (shouldTimeout, isDreaming) ->
+                    if (isDreaming && shouldTimeout) {
+                        communalInteractor.onSceneChanged(CommunalScenes.Blank)
+                    }
+                }
+        }
     }
 
     private suspend fun determineSceneAfterTransition(
@@ -105,5 +160,6 @@
     companion object {
         val AWAKE_DEBOUNCE_DELAY = 5.seconds
         val DOCK_DEBOUNCE_DELAY = 1.seconds
+        val DEFAULT_SCREEN_TIMEOUT = 15000
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 940b48c..52025b1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -63,10 +63,13 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.BufferOverflow
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
@@ -84,7 +87,7 @@
 class CommunalInteractor
 @Inject
 constructor(
-    @Application applicationScope: CoroutineScope,
+    @Application val applicationScope: CoroutineScope,
     broadcastDispatcher: BroadcastDispatcher,
     private val communalRepository: CommunalRepository,
     private val widgetRepository: CommunalWidgetRepository,
@@ -152,6 +155,14 @@
     /** Transition state of the hub mode. */
     val transitionState: StateFlow<ObservableTransitionState> = communalRepository.transitionState
 
+    val _userActivity: MutableSharedFlow<Unit> =
+        MutableSharedFlow(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+    val userActivity: Flow<Unit> = _userActivity.asSharedFlow()
+
+    fun signalUserInteraction() {
+        _userActivity.tryEmit(Unit)
+    }
+
     /**
      * Updates the transition state of the hub [SceneTransitionLayout].
      *
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 85f3c20..c913300 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -45,6 +45,10 @@
     val selectedKey: StateFlow<String?>
         get() = _selectedKey
 
+    fun signalUserInteraction() {
+        communalInteractor.signalUserInteraction()
+    }
+
     fun onSceneChanged(scene: SceneKey) {
         communalInteractor.onSceneChanged(scene)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index 9afd5ed..d2df276 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -24,9 +24,9 @@
 import com.android.systemui.keyguard.WorkLockActivity;
 import com.android.systemui.people.PeopleSpaceActivity;
 import com.android.systemui.people.widget.LaunchConversationActivity;
-import com.android.systemui.screenshot.LongScreenshotActivity;
 import com.android.systemui.screenshot.appclips.AppClipsActivity;
 import com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity;
+import com.android.systemui.screenshot.scroll.LongScreenshotActivity;
 import com.android.systemui.sensorprivacy.SensorUseStartedActivity;
 import com.android.systemui.settings.brightness.BrightnessDialog;
 import com.android.systemui.telephony.ui.activity.SwitchToManagedProfileForCallActivity;
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/display/data/repository/DeviceStateRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt
index 83337f7..fa7603f 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt
@@ -62,7 +62,7 @@
         conflatedCallbackFlow {
                 val callback =
                     DeviceStateManager.DeviceStateCallback { state ->
-                        trySend(deviceStateToPosture(state))
+                        trySend(deviceStateToPosture(state.identifier))
                     }
                 deviceStateManager.registerCallback(executor, callback)
                 awaitClose { deviceStateManager.unregisterCallback(callback) }
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/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
index 0cb57fb..1b832d4 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
@@ -21,7 +21,6 @@
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dock.DockManager
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel
@@ -47,7 +46,6 @@
     fromGlanceableHubTransitionInteractor: GlanceableHubToDreamingTransitionViewModel,
     private val toGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel,
     private val toLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
-    private val dockManager: DockManager,
     private val communalInteractor: CommunalInteractor,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val userTracker: UserTracker,
@@ -55,8 +53,7 @@
 
     fun startTransitionFromDream() {
         val showGlanceableHub =
-            dockManager.isDocked &&
-                communalInteractor.isCommunalEnabled.value &&
+            communalInteractor.isCommunalEnabled.value &&
                 !keyguardUpdateMonitor.isEncryptedOrLockdown(userTracker.userId)
         if (showGlanceableHub) {
             toGlanceableHubTransitionViewModel.startTransition()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 43a8b40..3b34750 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -40,6 +40,7 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
 import static com.android.systemui.DejankUtils.whitelistIpcs;
+import static com.android.systemui.Flags.migrateClocksToBlueprint;
 import static com.android.systemui.Flags.notifyPowerManagerUserActivityBackground;
 import static com.android.systemui.Flags.refactorGetCurrentUser;
 import static com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.DREAMING_ANIMATION_DURATION_MS;
@@ -3401,6 +3402,11 @@
                 mSurfaceBehindRemoteAnimationFinishedCallback = null;
             }
         }
+
+        // Ensure that keyguard becomes visible if the going away animation is canceled
+        if (showKeyguard && !KeyguardWmStateRefactor.isEnabled() && migrateClocksToBlueprint()) {
+            mKeyguardInteractor.showKeyguard();
+        }
     }
 
     private void adjustStatusBarLocked() {
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/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index 0e487d2..9c68c45 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -159,6 +159,11 @@
         lastAnimator?.cancel()
         lastAnimator = info.animator
 
+        // Cancel any existing manual transitions
+        updateTransitionId?.let { uuid ->
+            updateTransition(uuid, lastStep.value, TransitionState.CANCELED)
+        }
+
         info.animator?.let { animator ->
             // An animator was provided, so use it to run the transition
             animator.setFloatValues(startingValue, 1f)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index d5a9bd1..4a3232e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -71,6 +71,10 @@
         listenForGoneToDreamingLockscreenHosted()
     }
 
+    fun showKeyguard() {
+        scope.launch { startTransitionTo(KeyguardState.LOCKSCREEN) }
+    }
+
     // Primarily for when the user chooses to lock down the device
     private fun listenForGoneToLockscreenOrHub() {
         if (KeyguardWmStateRefactor.isEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 143edf9..283f160 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -57,6 +57,7 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.combineTransform
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flatMapLatest
@@ -82,6 +83,7 @@
     shadeRepository: ShadeRepository,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
     sceneInteractorProvider: Provider<SceneInteractor>,
+    private val fromGoneTransitionInteractor: Provider<FromGoneTransitionInteractor>,
 ) {
     // TODO(b/296118689): move to a repository
     private val _sharedNotificationContainerBounds = MutableStateFlow(NotificationContainerBounds())
@@ -179,11 +181,19 @@
 
     /** Keyguard can be clipped at the top as the shade is dragged */
     val topClippingBounds: Flow<Int?> =
-        combine(configurationInteractor.onAnyConfigurationChange, repository.topClippingBounds) {
-            _,
-            topClippingBounds ->
-            topClippingBounds
-        }
+        combineTransform(
+                configurationInteractor.onAnyConfigurationChange,
+                keyguardTransitionInteractor
+                    .transitionValue(GONE)
+                    .map { it == 1f }
+                    .onStart { emit(false) },
+                repository.topClippingBounds
+            ) { _, isGone, topClippingBounds ->
+                if (!isGone) {
+                    emit(topClippingBounds)
+                }
+            }
+            .distinctUntilChanged()
 
     /** Last point that [KeyguardRootView] view was tapped */
     val lastRootViewTapPosition: Flow<Point?> = repository.lastRootViewTapPosition.asStateFlow()
@@ -374,6 +384,11 @@
         repository.topClippingBounds.value = top
     }
 
+    /** Temporary shim, until [KeyguardWmStateRefactor] is enabled */
+    fun showKeyguard() {
+        fromGoneTransitionInteractor.get().showKeyguard()
+    }
+
     companion object {
         private const val TAG = "KeyguardInteractor"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index c28e49d..68ea5d0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.keyguard.logging.KeyguardLogger
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.log.core.LogLevel.VERBOSE
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -34,7 +34,7 @@
 class KeyguardTransitionAuditLogger
 @Inject
 constructor(
-    @Application private val scope: CoroutineScope,
+    @Background private val scope: CoroutineScope,
     private val interactor: KeyguardTransitionInteractor,
     private val keyguardInteractor: KeyguardInteractor,
     private val logger: KeyguardLogger,
@@ -70,6 +70,12 @@
         }
 
         scope.launch {
+            sharedNotificationContainerViewModel.bounds.collect {
+                logger.log(TAG, VERBOSE, "Notif: bounds", it)
+            }
+        }
+
+        scope.launch {
             sharedNotificationContainerViewModel.isOnLockscreenWithoutShade.collect {
                 logger.log(TAG, VERBOSE, "Notif: isOnLockscreenWithoutShade", it)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 00902b4..e6655ee 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -20,6 +20,7 @@
 import android.util.Log
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -38,9 +39,12 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.util.kotlin.pairwise
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.BufferOverflow
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
@@ -50,6 +54,7 @@
 import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
 
 /** Encapsulates business-logic related to the keyguard transitions. */
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -58,6 +63,7 @@
 @Inject
 constructor(
     @Application val scope: CoroutineScope,
+    @Main private val mainDispatcher: CoroutineDispatcher,
     private val keyguardRepository: KeyguardRepository,
     private val repository: KeyguardTransitionRepository,
     private val fromLockscreenTransitionInteractor: dagger.Lazy<FromLockscreenTransitionInteractor>,
@@ -70,6 +76,30 @@
 ) {
     private val TAG = this::class.simpleName
 
+    private val transitionValueCache = mutableMapOf<KeyguardState, MutableSharedFlow<Float>>()
+
+    /**
+     * Numerous flows are derived from, or care directly about, the transition value in and out of a
+     * single state. This prevent the redundant filters from running.
+     */
+    private fun getTransitionValueFlow(state: KeyguardState): MutableSharedFlow<Float> {
+        return transitionValueCache.getOrPut(state) {
+            MutableSharedFlow<Float>(
+                extraBufferCapacity = 2,
+                onBufferOverflow = BufferOverflow.DROP_OLDEST
+            )
+        }
+    }
+
+    init {
+        scope.launch(mainDispatcher) {
+            repository.transitions.collect { step ->
+                getTransitionValueFlow(step.from).emit(1f - step.value)
+                getTransitionValueFlow(step.to).emit(step.value)
+            }
+        }
+    }
+
     /** (any)->GONE transition information */
     val anyStateToGoneTransition: Flow<TransitionStep> =
         repository.transitions.filter { step -> step.to == GONE }
@@ -353,15 +383,7 @@
     fun transitionValue(
         state: KeyguardState,
     ): Flow<Float> {
-        return repository.transitions
-            .filter { it.from == state || it.to == state }
-            .map {
-                if (it.from == state) {
-                    1 - it.value
-                } else {
-                    it.value
-                }
-            }
+        return getTransitionValueFlow(state)
     }
 
     fun transitionStepsFromState(fromState: KeyguardState): Flow<TransitionStep> {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BurnInModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BurnInModel.kt
index 02d1471..0c8ddee 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BurnInModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BurnInModel.kt
@@ -20,6 +20,6 @@
 data class BurnInModel(
     val translationX: Int = 0,
     val translationY: Int = 0,
-    val scale: Float = 0f,
+    val scale: Float = 1f,
     val scaleClockOnly: Boolean = false,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
index d400210..3a2781c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
@@ -102,9 +102,16 @@
 
                 launch {
                     viewModel.scrimAlpha.collect {
+                        val wasVisible = alternateBouncerViewContainer.visibility == View.VISIBLE
                         alternateBouncerViewContainer.visibility =
                             if (it < .1f) View.INVISIBLE else View.VISIBLE
                         scrim.viewAlpha = it
+                        if (
+                            wasVisible && alternateBouncerViewContainer.visibility == View.INVISIBLE
+                        ) {
+                            // view is no longer visible
+                            viewModel.hideAlternateBouncer()
+                        }
                     }
                 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
index 1abf4a6..f20c4ac 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
@@ -21,8 +21,10 @@
 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToPrimaryBouncerTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.AodToGoneTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AodToOccludedTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DozingToGoneTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DozingToOccludedTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DozingToPrimaryBouncerTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.GoneToAodTransitionViewModel
@@ -73,13 +75,17 @@
 
     @Binds
     @IntoSet
+    abstract fun aodToGone(impl: AodToGoneTransitionViewModel): DeviceEntryIconTransition
+
+    @Binds
+    @IntoSet
     abstract fun aodToLockscreen(
         impl: AodToLockscreenTransitionViewModel
     ): DeviceEntryIconTransition
 
     @Binds
     @IntoSet
-    abstract fun aodToGone(impl: AodToGoneTransitionViewModel): DeviceEntryIconTransition
+    abstract fun aodToOccluded(impl: AodToOccludedTransitionViewModel): DeviceEntryIconTransition
 
     @Binds
     @IntoSet
@@ -93,6 +99,12 @@
 
     @Binds
     @IntoSet
+    abstract fun dozingToOccluded(
+        impl: DozingToOccludedTransitionViewModel
+    ): DeviceEntryIconTransition
+
+    @Binds
+    @IntoSet
     abstract fun dozingToPrimaryBouncer(
         impl: DozingToPrimaryBouncerTransitionViewModel
     ): DeviceEntryIconTransition
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
index 1085f94..8fd8bec 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
@@ -29,6 +29,7 @@
 import com.android.keyguard.LockIconView
 import com.android.keyguard.LockIconViewController
 import com.android.systemui.Flags.keyguardBottomAreaRefactor
+import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
@@ -70,7 +71,11 @@
     private val deviceEntryIconViewId = R.id.device_entry_icon_view
 
     override fun addViews(constraintLayout: ConstraintLayout) {
-        if (!keyguardBottomAreaRefactor() && !DeviceEntryUdfpsRefactor.isEnabled) {
+        if (
+            !keyguardBottomAreaRefactor() &&
+                !migrateClocksToBlueprint() &&
+                !DeviceEntryUdfpsRefactor.isEnabled
+        ) {
             return
         }
 
@@ -82,7 +87,7 @@
             if (DeviceEntryUdfpsRefactor.isEnabled) {
                 DeviceEntryIconView(context, null).apply { id = deviceEntryIconViewId }
             } else {
-                // keyguardBottomAreaRefactor()
+                // keyguardBottomAreaRefactor() or migrateClocksToBlueprint()
                 LockIconView(context, null).apply { id = R.id.lock_icon_view }
             }
         constraintLayout.addView(view)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
index 2526f0a..10a9e3b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
@@ -89,4 +89,8 @@
     fun showPrimaryBouncer() {
         statusBarKeyguardViewManager.showPrimaryBouncer(/* scrimmed */ true)
     }
+
+    fun hideAlternateBouncer() {
+        statusBarKeyguardViewManager.hideAlternateBouncer(false)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
index abf2372..6042117 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
@@ -72,7 +72,11 @@
                     duration = TO_LOCKSCREEN_DURATION,
                     onStep = { value -> -translatePx + value * translatePx },
                     interpolator = EMPHASIZED,
-                    onCancel = { -translatePx.toFloat() },
+                    // Move notifications back to their original position since they can be
+                    // accessed from the shade, and also keyguard elements in case the animation
+                    // is cancelled.
+                    onFinish = { 0f },
+                    onCancel = { 0f },
                     name = "GLANCEABLE_HUB->LOCKSCREEN: keyguardTranslationX"
                 )
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
index b7f7b06..5cbc1d4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
@@ -71,7 +71,8 @@
                     duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
                     onStep = { value -> value * translatePx },
                     // Move notifications back to their original position since they can be
-                    // accessed from the shade.
+                    // accessed from the shade, and also keyguard elements in case the animation
+                    // is cancelled.
                     onFinish = { 0f },
                     onCancel = { 0f },
                     interpolator = EMPHASIZED,
diff --git a/packages/SystemUI/src/com/android/systemui/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/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
index 730aa62..5dde14b 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -65,7 +65,7 @@
                         it.topActivity,
                         it.baseIntent?.component,
                         it.taskDescription?.backgroundColor,
-                        isForegroundTask = it.taskId in foregroundTaskIds
+                        isForegroundTask = it.taskId in foregroundTaskIds && it.isVisible
                     )
                 }
         }
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/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
index 1d820a1..5f0635b 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
@@ -21,6 +21,9 @@
 import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
+import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN;
+import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD;
+import static android.appwidget.flags.Flags.generatedPreviews;
 import static android.content.Intent.ACTION_BOOT_COMPLETED;
 import static android.content.Intent.ACTION_PACKAGE_ADDED;
 import static android.content.Intent.ACTION_PACKAGE_REMOVED;
@@ -80,12 +83,15 @@
 import android.service.notification.ZenModeConfig;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.SparseBooleanArray;
 import android.widget.RemoteViews;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.UiEventLoggerImpl;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.Dumpable;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
@@ -96,6 +102,8 @@
 import com.android.systemui.people.PeopleSpaceUtils;
 import com.android.systemui.people.PeopleTileViewHelper;
 import com.android.systemui.people.SharedPreferencesHelper;
+import com.android.systemui.res.R;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -160,13 +168,27 @@
     @GuardedBy("mLock")
     public static Map<Integer, PeopleSpaceTile> mTiles = new HashMap<>();
 
+    @NonNull private final UserTracker mUserTracker;
+    @NonNull private final SparseBooleanArray mUpdatedPreviews = new SparseBooleanArray();
+    @NonNull private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
+            new KeyguardUpdateMonitorCallback() {
+                @Override
+                public void onUserUnlocked() {
+                    if (DEBUG) {
+                        Log.d(TAG, "onUserUnlocked " + mUserTracker.getUserId());
+                    }
+                    updateGeneratedPreviewForUser(mUserTracker.getUserHandle());
+                }
+            };
+
     @Inject
     public PeopleSpaceWidgetManager(Context context, LauncherApps launcherApps,
             CommonNotifCollection notifCollection,
             PackageManager packageManager, Optional<Bubbles> bubblesOptional,
             UserManager userManager, NotificationManager notificationManager,
             BroadcastDispatcher broadcastDispatcher, @Background Executor bgExecutor,
-            DumpManager dumpManager) {
+            DumpManager dumpManager, @NonNull UserTracker userTracker,
+            @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor) {
         if (DEBUG) Log.d(TAG, "constructor");
         mContext = context;
         mAppWidgetManager = AppWidgetManager.getInstance(context);
@@ -187,6 +209,8 @@
         mBroadcastDispatcher = broadcastDispatcher;
         mBgExecutor = bgExecutor;
         dumpManager.registerNormalDumpable(TAG, this);
+        mUserTracker = userTracker;
+        keyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
     }
 
     /** Initializes {@PeopleSpaceWidgetManager}. */
@@ -246,7 +270,7 @@
             CommonNotifCollection notifCollection, PackageManager packageManager,
             Optional<Bubbles> bubblesOptional, UserManager userManager, BackupManager backupManager,
             INotificationManager iNotificationManager, NotificationManager notificationManager,
-            @Background Executor executor) {
+            @Background Executor executor, UserTracker userTracker) {
         mContext = context;
         mAppWidgetManager = appWidgetManager;
         mIPeopleManager = iPeopleManager;
@@ -262,6 +286,7 @@
         mManager = this;
         mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
         mBgExecutor = executor;
+        mUserTracker = userTracker;
     }
 
     /**
@@ -1407,4 +1432,24 @@
 
         Trace.traceEnd(Trace.TRACE_TAG_APP);
     }
+
+    @VisibleForTesting
+    void updateGeneratedPreviewForUser(UserHandle user) {
+        if (!generatedPreviews() || mUpdatedPreviews.get(user.getIdentifier())
+                || !mUserManager.isUserUnlocked(user)) {
+            return;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "Updating People Space widget preview for user " + user.getIdentifier());
+        }
+        boolean success = mAppWidgetManager.setWidgetPreview(
+                new ComponentName(mContext, PeopleSpaceWidgetProvider.class),
+                WIDGET_CATEGORY_HOME_SCREEN | WIDGET_CATEGORY_KEYGUARD,
+                new RemoteViews(mContext.getPackageName(),
+                        R.layout.people_space_placeholder_layout));
+        if (DEBUG && !success) {
+            Log.d(TAG, "Failed to update generated preview for user " + user.getIdentifier());
+        }
+        mUpdatedPreviews.put(user.getIdentifier(), success);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
index aed08f8..4a8e33a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
@@ -37,7 +37,11 @@
     data class PlatformTileSpec
     internal constructor(
         override val spec: String,
-    ) : TileSpec(spec)
+    ) : TileSpec(spec) {
+        override fun toString(): String {
+            return "P($spec)"
+        }
+    }
 
     /**
      * Container for the spec of a tile provided by an app.
@@ -50,7 +54,7 @@
         val componentName: ComponentName,
     ) : TileSpec(spec) {
         override fun toString(): String {
-            return "CustomTileSpec(${componentName.toShortString()})"
+            return "C(${componentName.flattenToShortString()})"
         }
     }
 
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/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/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt
index dcae088..1247854 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt
@@ -30,7 +30,7 @@
     private val bluetoothAutoOnRepository: BluetoothAutoOnRepository,
 ) {
 
-    val isEnabled = bluetoothAutoOnRepository.getValue.map { it == ENABLED }.distinctUntilChanged()
+    val isEnabled = bluetoothAutoOnRepository.isAutoOn.map { it == ENABLED }.distinctUntilChanged()
 
     /**
      * Checks if the auto on value is present in the repository.
@@ -49,7 +49,7 @@
             Log.e(TAG, "Trying to set toggle value while feature not available.")
         } else {
             val newValue = if (value) ENABLED else DISABLED
-            bluetoothAutoOnRepository.setValue(newValue)
+            bluetoothAutoOnRepository.setAutoOn(newValue)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt
index e17b4d3..f97fc38 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt
@@ -16,8 +16,6 @@
 
 package com.android.systemui.qs.tiles.dialog.bluetooth
 
-import android.os.UserHandle
-import android.util.Log
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
@@ -27,17 +25,21 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.withContext
 
-/** Repository class responsible for managing the Bluetooth Auto-On feature settings. */
-// TODO(b/316822488): Handle multi-user
+/**
+ * Repository class responsible for managing the Bluetooth Auto-On feature settings for the current
+ * user.
+ */
 @SysUISingleton
 class BluetoothAutoOnRepository
 @Inject
@@ -47,21 +49,24 @@
     @Application private val coroutineScope: CoroutineScope,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
 ) {
-    // Flow representing the auto on setting value
-    internal val getValue: Flow<Int> =
-        secureSettings
-            .observerFlow(UserHandle.USER_SYSTEM, SETTING_NAME)
-            .onStart { emit(Unit) }
-            .map {
-                if (userRepository.getSelectedUserInfo().id != UserHandle.USER_SYSTEM) {
-                    Log.i(TAG, "Current user is not USER_SYSTEM. Multi-user is not supported")
-                    return@map UNSET
-                }
-                secureSettings.getIntForUser(SETTING_NAME, UNSET, UserHandle.USER_SYSTEM)
+
+    // Flow representing the auto on setting value for the current user
+    @OptIn(ExperimentalCoroutinesApi::class)
+    internal val isAutoOn: StateFlow<Int> =
+        userRepository.selectedUserInfo
+            .flatMapLatest { userInfo ->
+                secureSettings
+                    .observerFlow(userInfo.id, SETTING_NAME)
+                    .onStart { emit(Unit) }
+                    .map { secureSettings.getIntForUser(SETTING_NAME, UNSET, userInfo.id) }
             }
             .distinctUntilChanged()
             .flowOn(backgroundDispatcher)
-            .shareIn(coroutineScope, SharingStarted.WhileSubscribed(replayExpirationMillis = 0))
+            .stateIn(
+                coroutineScope,
+                SharingStarted.WhileSubscribed(replayExpirationMillis = 0),
+                UNSET
+            )
 
     /**
      * Checks if the auto on setting value is ever set for the current user.
@@ -70,12 +75,11 @@
      */
     suspend fun isValuePresent(): Boolean =
         withContext(backgroundDispatcher) {
-            if (userRepository.getSelectedUserInfo().id != UserHandle.USER_SYSTEM) {
-                Log.i(TAG, "Current user is not USER_SYSTEM. Multi-user is not supported")
-                false
-            } else {
-                secureSettings.getIntForUser(SETTING_NAME, UNSET, UserHandle.USER_SYSTEM) != UNSET
-            }
+            secureSettings.getIntForUser(
+                SETTING_NAME,
+                UNSET,
+                userRepository.getSelectedUserInfo().id
+            ) != UNSET
         }
 
     /**
@@ -83,18 +87,17 @@
      *
      * @param value The new setting value to be applied.
      */
-    suspend fun setValue(value: Int) {
+    suspend fun setAutoOn(value: Int) {
         withContext(backgroundDispatcher) {
-            if (userRepository.getSelectedUserInfo().id != UserHandle.USER_SYSTEM) {
-                Log.i(TAG, "Current user is not USER_SYSTEM. Multi-user is not supported")
-            } else {
-                secureSettings.putIntForUser(SETTING_NAME, value, UserHandle.USER_SYSTEM)
-            }
+            secureSettings.putIntForUser(
+                SETTING_NAME,
+                value,
+                userRepository.getSelectedUserInfo().id
+            )
         }
     }
 
     companion object {
-        private const val TAG = "BluetoothAutoOnRepository"
         const val SETTING_NAME = "bluetooth_automatic_turn_on"
         const val UNSET = -1
     }
diff --git a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
index 9076182..f02b871 100644
--- a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
@@ -16,12 +16,14 @@
 
 package com.android.systemui.reardisplay;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.hardware.devicestate.DeviceState;
 import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.devicestate.DeviceStateManagerGlobal;
 import android.view.LayoutInflater;
@@ -29,7 +31,6 @@
 import android.view.ViewGroup.LayoutParams;
 import android.widget.LinearLayout;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -179,6 +180,7 @@
      */
     private void initializeValues(int startingBaseState) {
         mRearDisplayEducationDialog = mSystemUIDialogFactory.create();
+        // TODO(b/329170810): Refactor and remove with updated DeviceStateManager values.
         if (mFoldedStates == null) {
             mFoldedStates = mResources.getIntArray(
                     com.android.internal.R.array.config_foldedDeviceStates);
@@ -228,21 +230,19 @@
 
     private class DeviceStateManagerCallback implements DeviceStateManager.DeviceStateCallback {
         @Override
-        public void onBaseStateChanged(int state) {
-            if (mStartedFolded && !isFoldedState(state)) {
+        public void onDeviceStateChanged(@NonNull DeviceState state) {
+            if (mStartedFolded && !state.hasProperty(
+                    DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED)) {
                 // We've opened the device, we can close the overlay
                 mRearDisplayEducationDialog.dismiss();
                 closeOverlayAndNotifyService(false);
-            } else if (!mStartedFolded && isFoldedState(state)) {
+            } else if (!mStartedFolded && state.hasProperty(
+                    DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED)) {
                 // We've closed the device, finish activity
                 mRearDisplayEducationDialog.dismiss();
                 closeOverlayAndNotifyService(true);
             }
         }
-
-        // We only care about physical device changes in this scenario
-        @Override
-        public void onStateChanged(int state) {}
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index d0ff338..9ae049d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -37,7 +37,6 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_WAKEFULNESS_TRANSITION;
 
@@ -115,8 +114,6 @@
 import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.sysui.ShellInterface;
 
-import dagger.Lazy;
-
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -128,6 +125,8 @@
 import javax.inject.Inject;
 import javax.inject.Provider;
 
+import dagger.Lazy;
+
 /**
  * Class to send information from overview to launcher with a binder.
  */
@@ -670,8 +669,7 @@
             // Listen for tracing state changes
             @Override
             public void onTracingStateChanged(boolean enabled) {
-                mSysUiState.setFlag(SYSUI_STATE_TRACING_ENABLED, enabled)
-                        .commitUpdate(mContext.getDisplayId());
+                // TODO(b/286509643) Cleanup callers of this; Unused downstream
             }
 
             @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index 8a84496..7009816 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -27,8 +27,10 @@
 import android.util.Log
 import androidx.core.content.FileProvider
 import com.android.internal.logging.UiEventLogger
+import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.dagger.qualifiers.LongRunning
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
 import com.android.systemui.res.R
 import com.android.systemui.screenrecord.RecordingController
 import com.android.systemui.screenrecord.RecordingService
@@ -54,7 +56,9 @@
     uiEventLogger: UiEventLogger,
     notificationManager: NotificationManager,
     userContextProvider: UserContextProvider,
-    keyguardDismissUtil: KeyguardDismissUtil
+    keyguardDismissUtil: KeyguardDismissUtil,
+    private val dialogTransitionAnimator: DialogTransitionAnimator,
+    private val panelInteractor: PanelInteractor,
 ) :
     RecordingService(
         controller,
@@ -102,6 +106,8 @@
             }
             ACTION_SHARE -> {
                 shareRecording(intent)
+                dialogTransitionAnimator.disableAllCurrentDialogsExitAnimations()
+                panelInteractor.collapsePanels()
 
                 // Unlike all other actions, action_share has different behavior for the screen
                 // recording qs tile than it does for the record issue qs tile. Return sticky to
@@ -124,13 +130,11 @@
             FileSender.buildSendIntent(this, listOf(sharableUri))
                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
 
-        if (mNotificationId != NOTIF_BASE_ID) {
-            mNotificationManager.cancelAsUser(
-                null,
-                mNotificationId,
-                UserHandle(mUserContextTracker.userContext.userId)
-            )
-        }
+        mNotificationManager.cancelAsUser(
+            null,
+            mNotificationId,
+            UserHandle(mUserContextTracker.userContext.userId)
+        )
 
         // TODO: Debug why the notification shade isn't closing upon starting the BetterBug activity
         mKeyguardDismissUtil.executeWhenUnlocked(
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index ff18a11..7313a49 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -28,8 +28,8 @@
 import android.widget.Button
 import android.widget.PopupMenu
 import android.widget.Switch
-import androidx.annotation.AnyThread
 import androidx.annotation.MainThread
+import androidx.annotation.WorkerThread
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlagsClassic
@@ -74,6 +74,7 @@
 
     @SuppressLint("UseSwitchCompatOrMaterialCode") private lateinit var screenRecordSwitch: Switch
     private lateinit var issueTypeButton: Button
+    private var hasSelectedIssueType: Boolean = false
 
     @MainThread
     override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
@@ -82,15 +83,21 @@
             setTitle(context.getString(R.string.qs_record_issue_label))
             setIcon(R.drawable.qs_record_issue_icon_off)
             setNegativeButton(R.string.cancel) { _, _ -> dismiss() }
-            setPositiveButton(R.string.qs_record_issue_start) { _, _ ->
-                onStarted.accept(
-                    IssueRecordingConfig(
-                        screenRecordSwitch.isChecked,
-                        true /* TODO: Base this on issueType selected */
-                    )
-                )
-                dismiss()
-            }
+            setPositiveButton(
+                R.string.qs_record_issue_start,
+                { _, _ ->
+                    if (hasSelectedIssueType) {
+                        onStarted.accept(
+                            IssueRecordingConfig(
+                                screenRecordSwitch.isChecked,
+                                true /* TODO: Base this on issueType selected */
+                            )
+                        )
+                        dismiss()
+                    }
+                },
+                false
+            )
         }
     }
 
@@ -104,49 +111,47 @@
 
             screenRecordSwitch = requireViewById(R.id.screenrecord_switch)
             screenRecordSwitch.setOnCheckedChangeListener { _, isEnabled ->
-                onScreenRecordSwitchClicked(context, isEnabled)
+                if (isEnabled) {
+                    bgExecutor.execute { onScreenRecordSwitchClicked() }
+                }
             }
             issueTypeButton = requireViewById(R.id.issue_type_button)
             issueTypeButton.setOnClickListener { onIssueTypeClicked(context) }
         }
     }
 
-    @AnyThread
-    private fun onScreenRecordSwitchClicked(context: Context, isEnabled: Boolean) {
-        if (!isEnabled) return
-
-        bgExecutor.execute {
-            if (
-                flags.isEnabled(WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES) &&
-                    devicePolicyResolver
-                        .get()
-                        .isScreenCaptureCompletelyDisabled(UserHandle.of(userTracker.userId))
-            ) {
-                mainExecutor.execute {
-                    screenCaptureDisabledDialogDelegate.createDialog().show()
-                    screenRecordSwitch.isChecked = false
-                }
-                return@execute
+    @WorkerThread
+    private fun onScreenRecordSwitchClicked() {
+        if (
+            flags.isEnabled(WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES) &&
+                devicePolicyResolver
+                    .get()
+                    .isScreenCaptureCompletelyDisabled(UserHandle.of(userTracker.userId))
+        ) {
+            mainExecutor.execute {
+                screenCaptureDisabledDialogDelegate.createDialog().show()
+                screenRecordSwitch.isChecked = false
             }
+            return
+        }
 
-            mediaProjectionMetricsLogger.notifyProjectionInitiated(
-                userTracker.userId,
-                SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER
-            )
+        mediaProjectionMetricsLogger.notifyProjectionInitiated(
+            userTracker.userId,
+            SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER
+        )
 
-            if (flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)) {
-                val prefs =
-                    userFileManager.getSharedPreferences(
-                        RecordIssueTile.TILE_SPEC,
-                        Context.MODE_PRIVATE,
-                        userTracker.userId
-                    )
-                if (!prefs.getBoolean(HAS_APPROVED_SCREEN_RECORDING, false)) {
-                    mainExecutor.execute {
-                        ScreenCapturePermissionDialogDelegate(factory, prefs).createDialog().apply {
-                            setOnCancelListener { screenRecordSwitch.isChecked = false }
-                            show()
-                        }
+        if (flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)) {
+            val prefs =
+                userFileManager.getSharedPreferences(
+                    RecordIssueTile.TILE_SPEC,
+                    Context.MODE_PRIVATE,
+                    userTracker.userId
+                )
+            if (!prefs.getBoolean(HAS_APPROVED_SCREEN_RECORDING, false)) {
+                mainExecutor.execute {
+                    ScreenCapturePermissionDialogDelegate(factory, prefs).createDialog().apply {
+                        setOnCancelListener { screenRecordSwitch.isChecked = false }
+                        show()
                     }
                 }
             }
@@ -174,5 +179,6 @@
             setForceShowIcon(true)
             show()
         }
+        hasSelectedIssueType = true
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
index c4287ca..864f29a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
@@ -126,7 +126,7 @@
     /**
      * Writes the given Bitmap to outputFile.
      */
-    ListenableFuture<File> exportToRawFile(Executor executor, Bitmap bitmap,
+    public ListenableFuture<File> exportToRawFile(Executor executor, Bitmap bitmap,
             final File outputFile) {
         return CallbackToFutureAdapter.getFuture(
                 (completer) -> {
@@ -196,7 +196,7 @@
      * @param bitmap   the bitmap to export
      * @return a listenable future result
      */
-    ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap,
+    public ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap,
             ZonedDateTime captureTime, UserHandle owner, int displayId) {
         return export(executor, new Task(mResolver, requestId, bitmap, captureTime, mCompressFormat,
                 mQuality, /* publish */ true, owner, mFlags,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
index 1f6d212..a1481f6 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
@@ -34,6 +34,7 @@
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.res.R
+import com.android.systemui.screenshot.scroll.ScrollCaptureController
 import com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS
 import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER
 import dagger.assisted.Assisted
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LogConfig.java b/packages/SystemUI/src/com/android/systemui/screenshot/LogConfig.java
index 6050c2b..440cf1c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LogConfig.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LogConfig.java
@@ -16,8 +16,9 @@
 
 package com.android.systemui.screenshot;
 
+/** Stores debug log configuration for screenshots. */
 @SuppressWarnings("PointlessBooleanExpression")
-class LogConfig {
+public class LogConfig {
 
     /** Log ALL the things... */
     private static final boolean DEBUG_ALL = false;
@@ -29,36 +30,37 @@
     private static final boolean TAG_WITH_CLASS_NAME = false;
 
     /** Action creation and user selection: Share, Save, Edit, Delete, Smart action, etc */
-    static final boolean DEBUG_ACTIONS = DEBUG_ALL || false;
+    public static final boolean DEBUG_ACTIONS = DEBUG_ALL || false;
 
     /** Debug info about animations such as start, complete and cancel */
-    static final boolean DEBUG_ANIM = DEBUG_ALL || false;
+    public static final boolean DEBUG_ANIM = DEBUG_ALL || false;
 
     /** Whenever Uri is supplied to consumer, or onComplete runnable is run() */
-    static final boolean DEBUG_CALLBACK = DEBUG_ALL || false;
+    public static final boolean DEBUG_CALLBACK = DEBUG_ALL || false;
 
     /** Logs information about dismissing the screenshot tool */
-    static final boolean DEBUG_DISMISS = DEBUG_ALL || false;
+    public static final boolean DEBUG_DISMISS = DEBUG_ALL || false;
 
     /** Touch or key event driven action or side effects */
-    static final boolean DEBUG_INPUT = DEBUG_ALL || false;
+    public static final boolean DEBUG_INPUT = DEBUG_ALL || false;
 
     /** Scroll capture usage */
-    static final boolean DEBUG_SCROLL = DEBUG_ALL || false;
+    public static final boolean DEBUG_SCROLL = DEBUG_ALL || false;
 
     /** Service lifecycle events and callbacks */
-    static final boolean DEBUG_SERVICE = DEBUG_ALL || false;
+    public static final boolean DEBUG_SERVICE = DEBUG_ALL || false;
 
     /** Storage related actions, Bitmap.compress, ContentManager, etc */
-    static final boolean DEBUG_STORAGE = DEBUG_ALL || false;
+    public static final boolean DEBUG_STORAGE = DEBUG_ALL || false;
 
     /** High level logical UI actions: timeout, onConfigChanged, insets, show actions, reset  */
-    static final boolean DEBUG_UI = DEBUG_ALL || false;
+    public static final boolean DEBUG_UI = DEBUG_ALL || false;
 
     /** Interactions with Window and WindowManager */
-    static final boolean DEBUG_WINDOW = DEBUG_ALL || false;
+    public static final boolean DEBUG_WINDOW = DEBUG_ALL || false;
 
-    static String logTag(Class<?> cls) {
+    /** Get the appropriate class name */
+    public static String logTag(Class<?> cls) {
         return TAG_WITH_CLASS_NAME ? cls.getSimpleName() : TAG_SS;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 198a29c..c8e13bb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -87,6 +87,10 @@
 import com.android.systemui.flags.Flags;
 import com.android.systemui.res.R;
 import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
+import com.android.systemui.screenshot.scroll.LongScreenshotActivity;
+import com.android.systemui.screenshot.scroll.LongScreenshotData;
+import com.android.systemui.screenshot.scroll.ScrollCaptureClient;
+import com.android.systemui.screenshot.scroll.ScrollCaptureController;
 import com.android.systemui.util.Assert;
 
 import com.google.common.util.concurrent.ListenableFuture;
@@ -200,7 +204,7 @@
         void onActionsReady(ScreenshotController.QuickShareData quickShareData);
     }
 
-    interface TransitionDestination {
+    public interface TransitionDestination {
         /**
          * Allows the long screenshot activity to call back with a destination location (the bounds
          * on screen of the destination for the transitioning view) and a Runnable to be run once
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 1c5a8a1..cb2dba0 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -92,6 +92,7 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.res.R;
+import com.android.systemui.screenshot.scroll.ScrollCaptureController;
 import com.android.systemui.shared.system.InputChannelCompat;
 import com.android.systemui.shared.system.InputMonitorCompat;
 import com.android.systemui.shared.system.QuickStepContract;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
index 182b889..6be32a9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
@@ -25,6 +25,7 @@
 import android.view.View
 import android.view.ViewGroup
 import android.view.WindowInsets
+import com.android.systemui.screenshot.scroll.ScrollCaptureController
 
 /** Abstraction of the surface between ScreenshotController and ScreenshotView */
 interface ScreenshotViewProxy {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
index aa23d6b..d87d85b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
@@ -52,7 +52,7 @@
 import com.android.internal.logging.UiEventLogger.UiEventEnum;
 import com.android.settingslib.Utils;
 import com.android.systemui.res.R;
-import com.android.systemui.screenshot.CropView;
+import com.android.systemui.screenshot.scroll.CropView;
 import com.android.systemui.settings.UserTracker;
 
 import javax.inject.Inject;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
rename to packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
index 2f411ea..5e561cf 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 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,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
 
 import android.animation.ValueAnimator;
 import android.content.Context;
@@ -265,7 +265,7 @@
                 Log.w(TAG, "No boundary selected");
                 break;
         }
-        Log.i(TAG,  "Updated mCrop: " + mCrop);
+        Log.i(TAG, "Updated mCrop: " + mCrop);
 
         invalidate();
     }
@@ -385,7 +385,7 @@
 
     /**
      * @param action either ACTION_DOWN, ACTION_UP or ACTION_MOVE.
-     * @param x coordinate of the relevant pointer.
+     * @param x      x-coordinate of the relevant pointer.
      */
     private void updateListener(int action, float x) {
         if (mCropInteractionListener != null && isVertical(mCurrentDraggingBoundary)) {
@@ -643,11 +643,13 @@
     /**
      * Listen for crop motion events and state.
      */
-    public interface CropInteractionListener {
+    interface CropInteractionListener {
         void onCropDragStarted(CropBoundary boundary, float boundaryPosition,
                 int boundaryPositionPx, float horizontalCenter, float x);
+
         void onCropDragMoved(CropBoundary boundary, float boundaryPosition,
                 int boundaryPositionPx, float horizontalCenter, float x);
+
         void onCropDragComplete();
     }
 
@@ -675,8 +677,7 @@
             out.writeParcelable(mCrop, 0);
         }
 
-        public static final Parcelable.Creator<SavedState> CREATOR
-                = new Parcelable.Creator<SavedState>() {
+        public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<>() {
             public SavedState createFromParcel(Parcel in) {
                 return new SavedState(in);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageLoader.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageLoader.java
similarity index 81%
rename from packages/SystemUI/src/com/android/systemui/screenshot/ImageLoader.java
rename to packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageLoader.java
index 7ee7c31..df86d69 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageLoader.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 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,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
 
 import android.annotation.Nullable;
 import android.content.ContentResolver;
@@ -35,20 +35,20 @@
 import javax.inject.Inject;
 
 /** Loads images. */
-public class ImageLoader {
+class ImageLoader {
     private final ContentResolver mResolver;
 
     static class Result {
-        @Nullable Uri uri;
-        @Nullable File fileName;
-        @Nullable Bitmap bitmap;
+        @Nullable Uri mUri;
+        @Nullable File mFilename;
+        @Nullable Bitmap mBitmap;
 
         @Override
         public String toString() {
             final StringBuilder sb = new StringBuilder("Result{");
-            sb.append("uri=").append(uri);
-            sb.append(", fileName=").append(fileName);
-            sb.append(", bitmap=").append(bitmap);
+            sb.append("uri=").append(mUri);
+            sb.append(", fileName=").append(mFilename);
+            sb.append(", bitmap=").append(mBitmap);
             sb.append('}');
             return sb.toString();
         }
@@ -69,11 +69,10 @@
         return CallbackToFutureAdapter.getFuture(completer -> {
             Result result = new Result();
             try (InputStream in = mResolver.openInputStream(uri)) {
-                result.uri = uri;
-                result.bitmap = BitmapFactory.decodeStream(in);
+                result.mUri = uri;
+                result.mBitmap = BitmapFactory.decodeStream(in);
                 completer.set(result);
-            }
-            catch (IOException e) {
+            } catch (IOException e) {
                 completer.setException(e);
             }
             return "BitmapFactory#decodeStream";
@@ -91,8 +90,8 @@
         return CallbackToFutureAdapter.getFuture(completer -> {
             try (InputStream in = new BufferedInputStream(new FileInputStream(file))) {
                 Result result = new Result();
-                result.fileName = file;
-                result.bitmap = BitmapFactory.decodeStream(in);
+                result.mFilename = file;
+                result.mBitmap = BitmapFactory.decodeStream(in);
                 completer.set(result);
             } catch (IOException e) {
                 completer.setException(e);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageTile.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java
rename to packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageTile.java
index a95c91b..c9c297e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageTile.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 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.
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
 
 import static android.graphics.ColorSpace.Named.SRGB;
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageTileSet.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java
rename to packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageTileSet.java
index 356f67e..76a72f7 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageTileSet.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 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.
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
 
 import android.annotation.AnyThread;
 import android.graphics.Bitmap;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
rename to packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
index 00d480a..1e1a577 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 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,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
 
 import android.app.Activity;
 import android.app.ActivityOptions;
@@ -47,11 +47,14 @@
 import com.android.internal.view.OneShotPreDrawListener;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.res.R;
-import com.android.systemui.screenshot.CropView.CropBoundary;
-import com.android.systemui.screenshot.ScrollCaptureController.LongScreenshot;
-import com.android.systemui.settings.UserTracker;
+import com.android.systemui.screenshot.ActionIntentCreator;
+import com.android.systemui.screenshot.ActionIntentExecutor;
+import com.android.systemui.screenshot.ImageExporter;
+import com.android.systemui.screenshot.LogConfig;
+import com.android.systemui.screenshot.ScreenshotEvent;
+import com.android.systemui.screenshot.scroll.CropView.CropBoundary;
+import com.android.systemui.screenshot.scroll.ScrollCaptureController.LongScreenshot;
 
 import com.google.common.util.concurrent.ListenableFuture;
 
@@ -81,8 +84,6 @@
     private final ImageExporter mImageExporter;
     private final LongScreenshotData mLongScreenshotHolder;
     private final ActionIntentExecutor mActionExecutor;
-    private final FeatureFlags mFeatureFlags;
-    private final UserTracker mUserTracker;
 
     private ImageView mPreview;
     private ImageView mTransitionView;
@@ -113,16 +114,13 @@
     @Inject
     public LongScreenshotActivity(UiEventLogger uiEventLogger, ImageExporter imageExporter,
             @Main Executor mainExecutor, @Background Executor bgExecutor,
-            LongScreenshotData longScreenshotHolder, ActionIntentExecutor actionExecutor,
-            FeatureFlags featureFlags, UserTracker userTracker) {
+            LongScreenshotData longScreenshotHolder, ActionIntentExecutor actionExecutor) {
         mUiEventLogger = uiEventLogger;
         mUiExecutor = mainExecutor;
         mBackgroundExecutor = bgExecutor;
         mImageExporter = imageExporter;
         mLongScreenshotHolder = longScreenshotHolder;
         mActionExecutor = actionExecutor;
-        mFeatureFlags = featureFlags;
-        mUserTracker = userTracker;
     }
 
 
@@ -265,13 +263,13 @@
     private void onCachedImageLoaded(ImageLoader.Result imageResult) {
         mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_CACHED_IMAGE_LOADED);
 
-        BitmapDrawable drawable = new BitmapDrawable(getResources(), imageResult.bitmap);
+        BitmapDrawable drawable = new BitmapDrawable(getResources(), imageResult.mBitmap);
         mPreview.setImageDrawable(drawable);
         mPreview.setAlpha(1f);
-        mMagnifierView.setDrawable(drawable, imageResult.bitmap.getWidth(),
-                imageResult.bitmap.getHeight());
+        mMagnifierView.setDrawable(drawable, imageResult.mBitmap.getWidth(),
+                imageResult.mBitmap.getHeight());
         mCropView.setVisibility(View.VISIBLE);
-        mSavedImagePath = imageResult.fileName;
+        mSavedImagePath = imageResult.mFilename;
 
         setButtonsEnabled(true);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotData.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotData.java
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotData.java
rename to packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotData.java
index f549faf..ebac5bf 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotData.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotData.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 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,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
 
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.screenshot.ScreenshotController;
 
 import java.util.concurrent.atomic.AtomicReference;
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/MagnifierView.java
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
rename to packages/SystemUI/src/com/android/systemui/screenshot/scroll/MagnifierView.java
index 0c543cd..0a1a747 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/MagnifierView.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 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,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -64,16 +64,16 @@
     private ViewPropertyAnimator mTranslationAnimator;
     private final Animator.AnimatorListener mTranslationAnimatorListener =
             new AnimatorListenerAdapter() {
-        @Override
-        public void onAnimationCancel(Animator animation) {
-            mTranslationAnimator = null;
-        }
+                @Override
+                public void onAnimationCancel(Animator animation) {
+                    mTranslationAnimator = null;
+                }
 
-        @Override
-        public void onAnimationEnd(Animator animation) {
-            mTranslationAnimator = null;
-        }
-    };
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mTranslationAnimator = null;
+                }
+            };
 
     public MagnifierView(Context context, @Nullable AttributeSet attrs) {
         this(context, attrs, 0);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureClient.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
rename to packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureClient.java
index e93f737..0e43343 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureClient.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 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,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
 
 import static com.android.systemui.screenshot.LogConfig.DEBUG_SCROLL;
 
@@ -46,6 +46,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.screenshot.LogConfig;
 
 import com.google.common.util.concurrent.ListenableFuture;
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureController.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
rename to packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureController.java
index 8a2678c..f4c77da 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureController.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 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,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
 
 import android.content.Context;
 import android.graphics.Bitmap;
@@ -30,8 +30,10 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.screenshot.ScrollCaptureClient.CaptureResult;
-import com.android.systemui.screenshot.ScrollCaptureClient.Session;
+import com.android.systemui.screenshot.LogConfig;
+import com.android.systemui.screenshot.ScreenshotEvent;
+import com.android.systemui.screenshot.scroll.ScrollCaptureClient.CaptureResult;
+import com.android.systemui.screenshot.scroll.ScrollCaptureClient.Session;
 
 import com.google.common.util.concurrent.ListenableFuture;
 
@@ -85,7 +87,7 @@
             mImageTileSet = imageTileSet;
         }
 
-        /** Returns a bitmap containing the combinded result. */
+        /** Returns a bitmap containing the combined result. */
         public Bitmap toBitmap() {
             return mImageTileSet.toBitmap();
         }
@@ -167,7 +169,7 @@
      *                 {@link ScrollCaptureResponse#isConnected() connected}.
      * @return a future ImageTile set containing the result
      */
-    ListenableFuture<LongScreenshot> run(ScrollCaptureResponse response) {
+    public ListenableFuture<LongScreenshot> run(ScrollCaptureResponse response) {
         mCancelled = false;
         return CallbackToFutureAdapter.getFuture(completer -> {
             mCaptureCompleter = completer;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/TiledImageDrawable.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java
rename to packages/SystemUI/src/com/android/systemui/screenshot/scroll/TiledImageDrawable.java
index 71df369..00455bc 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/TiledImageDrawable.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 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.
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
 
 import android.annotation.Nullable;
 import android.graphics.Canvas;
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/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index de21a73..8197b66 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -37,12 +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
-
     /** Returns whether the shade is tracking touches for expand/collapse of the shade or QS. */
     val isTracking: Boolean
 
@@ -102,19 +96,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..48a2d75 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -33,31 +33,34 @@
     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
+    @Deprecated("Use !ShadeInteractor.isAnyExpanded instead")
     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 +69,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 +91,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..79ffe06 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt
@@ -41,4 +41,23 @@
      * 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 shade's height is zero. */
+    @Deprecated("Use !ShadeInteractor.isAnyExpanded instead") val isFullyCollapsed: Boolean
+
+    /** Returns whether the shade is in the process of collapsing. */
+    @Deprecated("Use ShadeAnimationInteractor instead") val isCollapsing: Boolean
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
index 20f73b0..3ad2b56 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
@@ -35,6 +35,8 @@
 @Inject
 constructor(
     sceneInteractor: SceneInteractor,
+    shadeInteractor: ShadeInteractor,
+    shadeAnimationInteractor: ShadeAnimationInteractor,
 ) : PanelExpansionInteractor {
 
     /**
@@ -93,6 +95,19 @@
             }
         }
 
+    @Deprecated(
+        "depends on the state you check, use {@link #isShadeFullyExpanded()},\n" +
+            "{@link #isOnAod()}, {@link #isOnKeyguard()} instead."
+    )
+    override val isFullyExpanded = shadeInteractor.isAnyFullyExpanded.value
+
+    @Deprecated("Use !ShadeInteractor.isAnyExpanded instead")
+    override val isFullyCollapsed = !shadeInteractor.isAnyExpanded.value
+
+    @Deprecated("Use ShadeAnimationInteractor instead")
+    override val isCollapsing =
+        shadeAnimationInteractor.isAnyCloseAnimationRunning.value ||
+            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/shade/domain/interactor/ShadeInteractorLegacyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
index 6414af3..421a761 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
@@ -46,7 +46,10 @@
     sharedNotificationContainerInteractor: SharedNotificationContainerInteractor,
     repository: ShadeRepository,
 ) : BaseShadeInteractor {
-    /** The amount [0-1] that the shade has been opened */
+    /**
+     * The amount [0-1] that the shade has been opened. Uses stateIn to avoid redundant calculations
+     * in downstream flows.
+     */
     override val shadeExpansion: Flow<Float> =
         combine(
                 repository.lockscreenShadeExpansion,
@@ -71,6 +74,7 @@
                 }
             }
             .distinctUntilChanged()
+            .stateIn(scope, SharingStarted.Eagerly, 0f)
 
     override val qsExpansion: StateFlow<Float> = repository.qsExpansion
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 4275fc6..4406813 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -178,6 +178,7 @@
     private static final int MSG_IMMERSIVE_CHANGED = 78 << MSG_SHIFT;
     private static final int MSG_SET_QS_TILES = 79 << MSG_SHIFT;
     private static final int MSG_ENTER_DESKTOP = 80 << MSG_SHIFT;
+    private static final int MSG_SET_SPLITSCREEN_FOCUS = 81 << MSG_SHIFT;
     public static final int FLAG_EXCLUDE_NONE = 0;
     public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
     public static final int FLAG_EXCLUDE_RECENTS_PANEL = 1 << 1;
@@ -508,6 +509,11 @@
         default void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop) {}
 
         /**
+         * @see IStatusBar#setSplitscreenFocus
+         */
+        default void setSplitscreenFocus(boolean leftOrTop) {}
+
+        /**
          * @see IStatusBar#showMediaOutputSwitcher
          */
         default void showMediaOutputSwitcher(String packageName) {}
@@ -1349,6 +1355,12 @@
     }
 
     @Override
+    public void setSplitscreenFocus(boolean leftOrTop) {
+        synchronized (mLock) {
+            mHandler.obtainMessage(MSG_SET_SPLITSCREEN_FOCUS, leftOrTop).sendToTarget();
+        }
+    }
+    @Override
     public void showMediaOutputSwitcher(String packageName) {
         int callingUid = Binder.getCallingUid();
         if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
@@ -1919,6 +1931,11 @@
                     }
                     break;
                 }
+                case MSG_SET_SPLITSCREEN_FOCUS:
+                    for (int i = 0; i < mCallbacks.size(); i++) {
+                        mCallbacks.get(i).setSplitscreenFocus((Boolean) msg.obj);
+                    }
+                    break;
                 case MSG_SHOW_MEDIA_OUTPUT_SWITCHER:
                     args = (SomeArgs) msg.obj;
                     String clientPackageName = (String) args.arg1;
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/interruption/FullScreenIntentDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt
index 2707ed8..b77748e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.FSI_KEYGUARD_OCCLUDED
 import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.FSI_KEYGUARD_SHOWING
 import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.FSI_LOCKED_SHADE
+import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.FSI_USER_SETUP_INCOMPLETE
 import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_EXPECTED_TO_HUN
 import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_NOT_IMPORTANT_ENOUGH
 import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_NO_FULL_SCREEN_INTENT
@@ -101,6 +102,7 @@
         FSI_KEYGUARD_OCCLUDED(true, "keyguard is occluded"),
         FSI_LOCKED_SHADE(true, "locked shade"),
         FSI_DEVICE_NOT_PROVISIONED(true, "device not provisioned"),
+        FSI_USER_SETUP_INCOMPLETE(true, "user setup incomplete"),
         NO_FSI_NO_HUN_OR_KEYGUARD(
             false,
             "no HUN or keyguard",
@@ -189,6 +191,10 @@
             return FSI_DEVICE_NOT_PROVISIONED
         }
 
+        if (!deviceProvisionedController.isCurrentUserSetup) {
+            return FSI_USER_SETUP_INCOMPLETE
+        }
+
         return NO_FSI_NO_HUN_OR_KEYGUARD
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
index b0155f1..c084482 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
@@ -104,7 +104,11 @@
         /**
          * The device is not provisioned, launch FSI.
          */
-        FSI_NOT_PROVISIONED(true);
+        FSI_NOT_PROVISIONED(true),
+        /**
+         * The current user has not completed setup, launch FSI.
+         */
+        FSI_USER_SETUP_INCOMPLETE(true);
 
         public final boolean shouldLaunch;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index dc9eeb3..a655c72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -362,6 +362,12 @@
                     suppressedByDND);
         }
 
+        // The current user hasn't completed setup, launch FSI.
+        if (!mDeviceProvisionedController.isCurrentUserSetup()) {
+            return getDecisionGivenSuppression(FullScreenIntentDecision.FSI_USER_SETUP_INCOMPLETE,
+                    suppressedByDND);
+        }
+
         // Detect the case determined by b/231322873 to launch FSI while device is in use,
         // as blocked by the correct implementation, and report the event.
         return getDecisionGivenSuppression(FullScreenIntentDecision.NO_FSI_NO_HUN_OR_KEYGUARD,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 77e9425..9479762 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -49,6 +49,7 @@
 import android.graphics.Path;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.os.SystemClock;
 import android.os.Trace;
 import android.provider.Settings;
 import android.util.AttributeSet;
@@ -208,6 +209,9 @@
     private float mQsExpansionFraction;
     private final int mSplitShadeMinContentHeight;
     private String mLastUpdateSidePaddingDumpString;
+    private long mLastUpdateSidePaddingElapsedRealtime;
+    private String mLastInitViewDumpString;
+    private long mLastInitViewElapsedRealtime;
 
     /**
      * The algorithm which calculates the properties for our children
@@ -887,17 +891,34 @@
         mOverflingDistance = configuration.getScaledOverflingDistance();
 
         Resources res = context.getResources();
+        final boolean isSmallScreenLandscape = res.getBoolean(R.bool.is_small_screen_landscape);
         boolean useSmallLandscapeLockscreenResources = mIsSmallLandscapeLockscreenEnabled
-                && res.getBoolean(R.bool.is_small_screen_landscape);
+                && isSmallScreenLandscape;
         // TODO (b/293252410) remove condition here when flag is launched
         //  Instead update the config_skinnyNotifsInLandscape to be false whenever
         //  is_small_screen_landscape is true. Then, only use the config_skinnyNotifsInLandscape.
+        final boolean configSkinnyNotifsInLandscape = res.getBoolean(
+                R.bool.config_skinnyNotifsInLandscape);
         if (useSmallLandscapeLockscreenResources) {
             mSkinnyNotifsInLandscape = false;
         } else {
-            mSkinnyNotifsInLandscape = res.getBoolean(
-                    R.bool.config_skinnyNotifsInLandscape);
+            mSkinnyNotifsInLandscape = configSkinnyNotifsInLandscape;
         }
+
+        mLastInitViewDumpString =
+                "mIsSmallLandscapeLockscreenEnabled=" + mIsSmallLandscapeLockscreenEnabled
+                        + " isSmallScreenLandscape=" + isSmallScreenLandscape
+                        + " useSmallLandscapeLockscreenResources="
+                        + useSmallLandscapeLockscreenResources
+                        + " skinnyNotifsInLandscape=" + configSkinnyNotifsInLandscape
+                        + " mSkinnyNotifsInLandscape=" + mSkinnyNotifsInLandscape;
+        mLastInitViewElapsedRealtime = SystemClock.elapsedRealtime();
+
+        if (DEBUG_UPDATE_SIDE_PADDING) {
+            Log.v(TAG, "initView @ elapsedRealtime " + mLastInitViewElapsedRealtime + ": "
+                    + mLastInitViewDumpString);
+        }
+
         mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height);
         mStackScrollAlgorithm.initView(context);
         mStateAnimator.initView(context);
@@ -925,22 +946,33 @@
         mLastUpdateSidePaddingDumpString = "viewWidth=" + viewWidth
                 + " skinnyNotifsInLandscape=" + mSkinnyNotifsInLandscape
                 + " orientation=" + orientation;
+        mLastUpdateSidePaddingElapsedRealtime = SystemClock.elapsedRealtime();
 
         if (DEBUG_UPDATE_SIDE_PADDING) {
-            Log.v(TAG, "updateSidePadding: " + mLastUpdateSidePaddingDumpString);
+            Log.v(TAG,
+                    "updateSidePadding @ elapsedRealtime " + mLastUpdateSidePaddingElapsedRealtime
+                            + ": " + mLastUpdateSidePaddingDumpString);
         }
 
-        if (viewWidth == 0 || !mSkinnyNotifsInLandscape) {
+        if (viewWidth == 0) {
+            Log.e(TAG, "updateSidePadding: viewWidth is zero");
             mSidePaddings = mMinimumPaddings;
             return;
         }
 
-        // Portrait is easy, just use the dimen for paddings
         if (orientation == Configuration.ORIENTATION_PORTRAIT) {
             mSidePaddings = mMinimumPaddings;
             return;
         }
 
+        if (mShouldUseSplitNotificationShade) {
+            if (mSkinnyNotifsInLandscape) {
+                Log.e(TAG, "updateSidePadding: mSkinnyNotifsInLandscape has betrayed us!");
+            }
+            mSidePaddings = mMinimumPaddings;
+            return;
+        }
+
         final int innerWidth = viewWidth - mMinimumPaddings * 2;
         final int qsTileWidth = (innerWidth - mQsTilePadding * 3) / 4;
         mSidePaddings = mMinimumPaddings + qsTileWidth + mQsTilePadding;
@@ -4884,6 +4916,7 @@
 
     public void dump(PrintWriter pwOriginal, String[] args) {
         IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal);
+        final long elapsedRealtime = SystemClock.elapsedRealtime();
         pw.println("Internal state:");
         DumpUtilsKt.withIncreasedIndent(pw, () -> {
             println(pw, "pulsing", mPulsing);
@@ -4914,7 +4947,17 @@
             println(pw, "minimumPaddings", mMinimumPaddings);
             println(pw, "qsTilePadding", mQsTilePadding);
             println(pw, "sidePaddings", mSidePaddings);
+            println(pw, "elapsedRealtime", elapsedRealtime);
+            println(pw, "lastInitView", mLastInitViewDumpString);
+            println(pw, "lastInitViewElapsedRealtime", mLastInitViewElapsedRealtime);
+            println(pw, "lastInitViewMillisAgo", elapsedRealtime - mLastInitViewElapsedRealtime);
+            println(pw, "shouldUseSplitNotificationShade", mShouldUseSplitNotificationShade);
             println(pw, "lastUpdateSidePadding", mLastUpdateSidePaddingDumpString);
+            println(pw, "lastUpdateSidePaddingElapsedRealtime",
+                    mLastUpdateSidePaddingElapsedRealtime);
+            println(pw, "lastUpdateSidePaddingMillisAgo",
+                    elapsedRealtime - mLastUpdateSidePaddingElapsedRealtime);
+            println(pw, "isSmallLandscapeLockscreenEnabled", mIsSmallLandscapeLockscreenEnabled);
             mNotificationStackSizeCalculator.dump(pw, args);
         });
         pw.println();
@@ -5482,6 +5525,7 @@
             mAmbientState.setUseSplitShade(split);
             updateDismissBehavior();
             updateUseRoundedRectClipping();
+            requestLayout();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index ece7a7f..5b8b91c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewbinder
 
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
 import android.view.View
 import android.view.WindowInsets
 import androidx.lifecycle.Lifecycle
@@ -118,13 +116,6 @@
                                             "SharedNotificationContainerVB (collapseFadeIn)"
                                         )
                                     }
-                                    addListener(
-                                        object : AnimatorListenerAdapter() {
-                                            override fun onAnimationEnd(animation: Animator) {
-                                                viewModel.setShadeCollapseFadeInComplete(true)
-                                            }
-                                        }
-                                    )
                                     start()
                                 }
                             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 2745817..2c4813a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -67,7 +67,6 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.currentCoroutineContext
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
@@ -79,6 +78,7 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.flow.transformWhile
 import kotlinx.coroutines.isActive
@@ -124,6 +124,23 @@
         setOf(AOD, LOCKSCREEN, DOZING, ALTERNATE_BOUNCER, PRIMARY_BOUNCER)
 
     /**
+     * Is either shade/qs expanded? This intentionally does not use the [ShadeInteractor] version,
+     * as the legacy implementation has extra logic that produces incorrect results.
+     */
+    private val isAnyExpanded =
+        combine(
+                shadeInteractor.shadeExpansion.map { it > 0f },
+                shadeInteractor.qsExpansion.map { it > 0f },
+            ) { shadeExpansion, qsExpansion ->
+                shadeExpansion || qsExpansion
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = false,
+            )
+
+    /**
      * Shade locked is a legacy concept, but necessary to mimic current functionality. Listen for
      * both SHADE_LOCKED and shade/qs expansion in order to determine lock state, as one can arrive
      * before the other.
@@ -131,17 +148,17 @@
     private val isShadeLocked: Flow<Boolean> =
         combine(
                 keyguardInteractor.statusBarState.map { it == SHADE_LOCKED },
-                shadeInteractor.qsExpansion.map { it > 0f },
-                shadeInteractor.shadeExpansion.map { it > 0f },
-            ) { isShadeLocked, isQsExpanded, isShadeExpanded ->
-                isShadeLocked && (isQsExpanded || isShadeExpanded)
+                isAnyExpanded,
+            ) { isShadeLocked, isAnyExpanded ->
+                isShadeLocked && isAnyExpanded
             }
-            .distinctUntilChanged()
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = false,
+            )
             .dumpWhileCollecting("isShadeLocked")
 
-    private val shadeCollapseFadeInComplete =
-        MutableStateFlow(false).dumpValue("shadeCollapseFadeInComplete")
-
     val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
         interactor.configurationBasedDimensions
             .map {
@@ -176,20 +193,16 @@
             ) { constrainedNotificationState, transitioningToOrFromLockscreen ->
                 constrainedNotificationState || transitioningToOrFromLockscreen
             }
-            .distinctUntilChanged()
+            .shareIn(scope = applicationScope, started = SharingStarted.Eagerly)
             .dumpWhileCollecting("isOnLockscreen")
 
     /** Are we purely on the keyguard without the shade/qs? */
     val isOnLockscreenWithoutShade: Flow<Boolean> =
         combine(
                 isOnLockscreen,
-                // Shade with notifications
-                shadeInteractor.shadeExpansion.map { it > 0f },
-                // Shade without notifications, quick settings only (pull down from very top on
-                // lockscreen)
-                shadeInteractor.qsExpansion.map { it > 0f },
-            ) { isKeyguard, isShadeVisible, qsExpansion ->
-                isKeyguard && !(isShadeVisible || qsExpansion)
+                isAnyExpanded,
+            ) { isKeyguard, isAnyExpanded ->
+                isKeyguard && !isAnyExpanded
             }
             .stateIn(
                 scope = applicationScope,
@@ -219,13 +232,9 @@
     val isOnGlanceableHubWithoutShade: Flow<Boolean> =
         combine(
                 isOnGlanceableHub,
-                // Shade with notifications
-                shadeInteractor.shadeExpansion.map { it > 0f },
-                // Shade without notifications, quick settings only (pull down from very top on
-                // lockscreen)
-                shadeInteractor.qsExpansion.map { it > 0f },
-            ) { isGlanceableHub, isShadeVisible, qsExpansion ->
-                isGlanceableHub && !(isShadeVisible || qsExpansion)
+                isAnyExpanded,
+            ) { isGlanceableHub, isAnyExpanded ->
+                isGlanceableHub && !isAnyExpanded
             }
             .stateIn(
                 scope = applicationScope,
@@ -283,9 +292,6 @@
                     awaitCollapse().collect { doFadeIn ->
                         if (doFadeIn) {
                             emit(true)
-                            // ... and then for the animation to complete
-                            shadeCollapseFadeInComplete.first { it }
-                            shadeCollapseFadeInComplete.value = false
                         }
                     }
                 }
@@ -295,7 +301,7 @@
                 started = SharingStarted.WhileSubscribed(),
                 initialValue = false,
             )
-            .dumpWhileCollecting("shadeCollapseFadeIn")
+            .dumpValue("shadeCollapseFadeIn")
 
     /**
      * The container occupies the entire screen, and must be positioned relative to other elements.
@@ -323,7 +329,7 @@
                     // When QS expansion > 0, it should directly set the top padding so do not
                     // animate it
                     val animate = qsExpansion == 0f && !isInTransitionToAnyState
-                    keyguardInteractor.notificationContainerBounds.value.copy(
+                    bounds.copy(
                         top = top,
                         isAnimated = animate,
                     )
@@ -350,6 +356,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)
@@ -544,10 +553,6 @@
         interactor.notificationStackChanged()
     }
 
-    fun setShadeCollapseFadeInComplete(complete: Boolean) {
-        shadeCollapseFadeInComplete.value = complete
-    }
-
     data class ConfigurationBasedDimensions(
         val marginStart: Int,
         val marginTop: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index ab9ecab..1faca00 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -57,6 +57,7 @@
 import com.android.systemui.shade.QuickSettingsController;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -81,6 +82,7 @@
     private final com.android.systemui.shade.ShadeController mShadeController;
     private final CommandQueue mCommandQueue;
     private final ShadeViewController mShadeViewController;
+    private final PanelExpansionInteractor mPanelExpansionInteractor;
     private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
     private final MetricsLogger mMetricsLogger;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -120,6 +122,7 @@
             ShadeController shadeController,
             CommandQueue commandQueue,
             ShadeViewController shadeViewController,
+            PanelExpansionInteractor panelExpansionInteractor,
             RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
             MetricsLogger metricsLogger,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -147,6 +150,7 @@
         mShadeController = shadeController;
         mCommandQueue = commandQueue;
         mShadeViewController = shadeViewController;
+        mPanelExpansionInteractor = panelExpansionInteractor;
         mRemoteInputQuickSettingsDisabler = remoteInputQuickSettingsDisabler;
         mMetricsLogger = metricsLogger;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -304,7 +308,7 @@
             mShadeController.animateCollapseShade();
         } else if (KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN == key.getKeyCode()) {
             mMetricsLogger.action(MetricsEvent.ACTION_SYSTEM_NAVIGATION_KEY_DOWN);
-            if (mShadeViewController.isFullyCollapsed()) {
+            if (mPanelExpansionInteractor.isFullyCollapsed()) {
                 if (mVibrateOnOpening) {
                     vibrateOnNavigationKeyDown();
                 }
@@ -371,7 +375,7 @@
                     mStatusBarKeyguardViewManager.reset(true /* hide */);
                 }
                 mCameraLauncherLazy.get().launchCamera(source,
-                        mShadeViewController.isFullyCollapsed());
+                        mPanelExpansionInteractor.isFullyCollapsed());
                 mCentralSurfaces.updateScrimController();
             } else {
                 // We need to defer the camera launch until the screen comes on, since otherwise
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FoldStateListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FoldStateListener.kt
index 56b0d598..db237e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FoldStateListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FoldStateListener.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.statusbar.phone
 
 import android.content.Context
+import android.hardware.devicestate.DeviceState
 import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback
 import com.android.internal.R
 
@@ -45,13 +46,13 @@
 
     private var wasFolded: Boolean? = null
 
-    override fun onStateChanged(state: Int) {
-        val isFolded = foldedDeviceStates.contains(state)
+    override fun onDeviceStateChanged(state: DeviceState) {
+        val isFolded = foldedDeviceStates.contains(state.identifier)
         if (wasFolded == isFolded) {
             return
         }
         wasFolded = isFolded
-        val willGoToSleep = goToSleepDeviceStates.contains(state)
+        val willGoToSleep = goToSleepDeviceStates.contains(state.identifier)
         listener.onFoldStateChanged(isFolded, willGoToSleep)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index a155e94..24be3db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -43,6 +43,7 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.AnimationStateHandler;
+import com.android.systemui.statusbar.policy.AvalancheController;
 import com.android.systemui.statusbar.policy.BaseHeadsUpManager;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
@@ -124,9 +125,10 @@
             AccessibilityManagerWrapper accessibilityManagerWrapper,
             UiEventLogger uiEventLogger,
             JavaAdapter javaAdapter,
-            ShadeInteractor shadeInteractor) {
+            ShadeInteractor shadeInteractor,
+            AvalancheController avalancheController) {
         super(context, logger, handler, globalSettings, systemClock, executor,
-                accessibilityManagerWrapper, uiEventLogger);
+                accessibilityManagerWrapper, uiEventLogger, avalancheController);
         Resources resources = mContext.getResources();
         mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time);
         statusBarStateController.addCallback(mStatusBarStateListener);
@@ -279,7 +281,7 @@
         if (headsUpEntry != null && headsUpEntry.mRemoteInputActive != remoteInputActive) {
             headsUpEntry.mRemoteInputActive = remoteInputActive;
             if (remoteInputActive) {
-                headsUpEntry.removeAutoRemovalCallbacks("setRemoteInputActive(true)");
+                headsUpEntry.cancelAutoRemovalCallbacks("setRemoteInputActive(true)");
             } else {
                 headsUpEntry.updateEntry(false /* updatePostTime */, "setRemoteInputActive(false)");
             }
@@ -482,7 +484,7 @@
 
             this.mExpanded = expanded;
             if (expanded) {
-                removeAutoRemovalCallbacks("setExpanded(true)");
+                cancelAutoRemovalCallbacks("setExpanded(true)");
             } else {
                 updateEntry(false /* updatePostTime */, "setExpanded(false)");
             }
@@ -495,7 +497,7 @@
 
             mGutsShownPinned = gutsShownPinned;
             if (gutsShownPinned) {
-                removeAutoRemovalCallbacks("setGutsShownPinned(true)");
+                cancelAutoRemovalCallbacks("setGutsShownPinned(true)");
             } else {
                 updateEntry(false /* updatePostTime */, "setGutsShownPinned(false)");
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index f29ec8f3..92fd90a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.shade.ShadeController
 import com.android.systemui.shade.ShadeLogger
 import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
 import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.window.StatusBarWindowStateController
@@ -58,6 +59,7 @@
     private val statusBarWindowStateController: StatusBarWindowStateController,
     private val shadeController: ShadeController,
     private val shadeViewController: ShadeViewController,
+    private val panelExpansionInteractor: PanelExpansionInteractor,
     private val windowRootView: Provider<WindowRootView>,
     private val shadeLogger: ShadeLogger,
     private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
@@ -218,7 +220,7 @@
                     )
                     return true
                 }
-                if (shadeViewController.isFullyCollapsed && event.y < 1f) {
+                if (panelExpansionInteractor.isFullyCollapsed && event.y < 1f) {
                     // b/235889526 Eat events on the top edge of the phone when collapsed
                     shadeLogger.logMotionEvent(event, "top edge touch ignored")
                     return true
@@ -271,6 +273,7 @@
         private val statusBarWindowStateController: StatusBarWindowStateController,
         private val shadeController: ShadeController,
         private val shadeViewController: ShadeViewController,
+        private val panelExpansionInteractor: PanelExpansionInteractor,
         private val windowRootView: Provider<WindowRootView>,
         private val shadeLogger: ShadeLogger,
         private val viewUtil: ViewUtil,
@@ -292,6 +295,7 @@
                 statusBarWindowStateController,
                 shadeController,
                 shadeViewController,
+                panelExpansionInteractor,
                 windowRootView,
                 shadeLogger,
                 statusBarMoveFromCenterAnimationController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
index 235ed25..69dd507 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
@@ -20,6 +20,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.StatusBarState;
@@ -38,6 +39,7 @@
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final StatusBarWindowController mStatusBarWindowController;
     private final ShadeViewController mShadeViewController;
+    private final PanelExpansionInteractor mPanelExpansionInteractor;
     private final NotificationStackScrollLayoutController mNsslController;
     private final KeyguardBypassController mKeyguardBypassController;
     private final HeadsUpManager mHeadsUpManager;
@@ -49,6 +51,7 @@
             NotificationShadeWindowController notificationShadeWindowController,
             StatusBarWindowController statusBarWindowController,
             ShadeViewController shadeViewController,
+            PanelExpansionInteractor panelExpansionInteractor,
             NotificationStackScrollLayoutController nsslController,
             KeyguardBypassController keyguardBypassController,
             HeadsUpManager headsUpManager,
@@ -57,6 +60,7 @@
         mNotificationShadeWindowController = notificationShadeWindowController;
         mStatusBarWindowController = statusBarWindowController;
         mShadeViewController = shadeViewController;
+        mPanelExpansionInteractor = panelExpansionInteractor;
         mNsslController = nsslController;
         mKeyguardBypassController = keyguardBypassController;
         mHeadsUpManager = headsUpManager;
@@ -74,13 +78,13 @@
         if (inPinnedMode) {
             mNotificationShadeWindowController.setHeadsUpShowing(true);
             mStatusBarWindowController.setForceStatusBarVisible(true);
-            if (mShadeViewController.isFullyCollapsed()) {
+            if (mPanelExpansionInteractor.isFullyCollapsed()) {
                 mShadeViewController.updateTouchableRegion();
             }
         } else {
             boolean bypassKeyguard = mKeyguardBypassController.getBypassEnabled()
                     && mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
-            if (!mShadeViewController.isFullyCollapsed()
+            if (!mPanelExpansionInteractor.isFullyCollapsed()
                     || mShadeViewController.isTracking()
                     || bypassKeyguard) {
                 // We are currently tracking or is open and the shade doesn't need to
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 7dd328a..14e934fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -76,6 +76,8 @@
 import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
 import com.android.systemui.keyguard.shared.model.DismissAction;
 import com.android.systemui.keyguard.shared.model.KeyguardDone;
+import com.android.systemui.keyguard.shared.model.KeyguardState;
+import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.navigationbar.TaskbarDelegate;
@@ -105,6 +107,8 @@
 
 import dagger.Lazy;
 
+import kotlin.Unit;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -116,6 +120,7 @@
 
 import kotlinx.coroutines.CoroutineDispatcher;
 import kotlinx.coroutines.ExperimentalCoroutinesApi;
+import kotlinx.coroutines.Job;
 
 /**
  * Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back
@@ -163,6 +168,9 @@
     private final Lazy<ShadeController> mShadeController;
     private final Lazy<SceneInteractor> mSceneInteractorLazy;
 
+    private Job mListenForAlternateBouncerTransitionSteps = null;
+    private Job mListenForKeyguardAuthenticatedBiometricsHandled = null;
+
     // Local cache of expansion events, to avoid duplicates
     private float mFraction = -1f;
     private boolean mTracking = false;
@@ -491,6 +499,26 @@
             mDockManager.addListener(mDockEventListener);
             mIsDocked = mDockManager.isDocked();
         }
+        if (mListenForAlternateBouncerTransitionSteps != null) {
+            mListenForAlternateBouncerTransitionSteps.cancel(null);
+        }
+        mListenForAlternateBouncerTransitionSteps = null;
+        if (mListenForKeyguardAuthenticatedBiometricsHandled != null) {
+            mListenForKeyguardAuthenticatedBiometricsHandled.cancel(null);
+        }
+        mListenForKeyguardAuthenticatedBiometricsHandled = null;
+        if (!DeviceEntryUdfpsRefactor.isEnabled()) {
+            mListenForAlternateBouncerTransitionSteps = mJavaAdapter.alwaysCollectFlow(
+                    mKeyguardTransitionInteractor.transitionStepsFromState(
+                            KeyguardState.ALTERNATE_BOUNCER),
+                    this::consumeFromAlternateBouncerTransitionSteps
+            );
+
+            mListenForKeyguardAuthenticatedBiometricsHandled = mJavaAdapter.alwaysCollectFlow(
+                    mPrimaryBouncerInteractor.getKeyguardAuthenticatedBiometricsHandled(),
+                    this::consumeKeyguardAuthenticatedBiometricsHandled
+            );
+        }
 
         if (KeyguardWmStateRefactor.isEnabled()) {
             // Show the keyguard views whenever we've told WM that the lockscreen is visible.
@@ -514,6 +542,22 @@
         }
     }
 
+    @VisibleForTesting
+    void consumeFromAlternateBouncerTransitionSteps(TransitionStep step) {
+        hideAlternateBouncer(false);
+    }
+
+    /**
+     * Required without fix for b/328643370: missing AlternateBouncer (when occluded) => Gone
+     * transition.
+     */
+    @VisibleForTesting
+    void consumeKeyguardAuthenticatedBiometricsHandled(Unit handled) {
+        if (mAlternateBouncerInteractor.isVisibleState()) {
+            hideAlternateBouncer(false);
+        }
+    }
+
     private void consumeShowStatusBarKeyguardView(boolean show) {
         if (show != mLastShowing) {
             if (show) {
@@ -1458,7 +1502,6 @@
         mPrimaryBouncerInteractor.notifyKeyguardAuthenticatedBiometrics(strongAuth);
 
         if (mAlternateBouncerInteractor.isVisibleState()) {
-            hideAlternateBouncer(false);
             executeAfterKeyguardGoneAction();
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index b5ab4e3..5d27467 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -61,7 +61,7 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationClickNotifier;
@@ -115,7 +115,7 @@
     private final Lazy<AssistManager> mAssistManagerLazy;
     private final NotificationRemoteInputManager mRemoteInputManager;
     private final NotificationLockscreenUserManager mLockscreenUserManager;
-    private final com.android.systemui.shade.ShadeController mShadeController;
+    private final ShadeController mShadeController;
     private final KeyguardStateController mKeyguardStateController;
     private final LockPatternUtils mLockPatternUtils;
     private final StatusBarRemoteInputCallback mStatusBarRemoteInputCallback;
@@ -126,7 +126,7 @@
     private final StatusBarNotificationActivityStarterLogger mLogger;
 
     private final NotificationPresenter mPresenter;
-    private final ShadeViewController mShadeViewController;
+    private final PanelExpansionInteractor mPanelExpansionInteractor;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final ActivityTransitionAnimator mActivityTransitionAnimator;
     private final NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider;
@@ -163,7 +163,7 @@
             StatusBarNotificationActivityStarterLogger logger,
             OnUserInteractionCallback onUserInteractionCallback,
             NotificationPresenter presenter,
-            ShadeViewController shadeViewController,
+            PanelExpansionInteractor panelExpansionInteractor,
             NotificationShadeWindowController notificationShadeWindowController,
             ActivityTransitionAnimator activityTransitionAnimator,
             ShadeAnimationInteractor shadeAnimationInteractor,
@@ -192,13 +192,13 @@
         mLockPatternUtils = lockPatternUtils;
         mStatusBarRemoteInputCallback = remoteInputCallback;
         mActivityIntentHelper = activityIntentHelper;
+        mPanelExpansionInteractor = panelExpansionInteractor;
         mNotificationShadeWindowController = notificationShadeWindowController;
         mShadeAnimationInteractor = shadeAnimationInteractor;
         mMetricsLogger = metricsLogger;
         mLogger = logger;
         mOnUserInteractionCallback = onUserInteractionCallback;
         mPresenter = presenter;
-        mShadeViewController = shadeViewController;
         mActivityTransitionAnimator = activityTransitionAnimator;
         mNotificationAnimationProvider = notificationAnimationProvider;
         mPowerInteractor = powerInteractor;
@@ -296,7 +296,7 @@
         }
 
         // Always defer the keyguard dismiss when animating.
-        return animate || !mShadeViewController.isFullyCollapsed();
+        return animate || !mPanelExpansionInteractor.isFullyCollapsed();
     }
 
     private void handleNotificationClickAfterPanelCollapsed(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 8e9c038..5a3ae73 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();
     }
 
@@ -232,7 +236,7 @@
 
     @Override
     public boolean isPresenterFullyCollapsed() {
-        return mNotificationPanel.isFullyCollapsed();
+        return mPanelExpansionInteractor.isFullyCollapsed();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt
index f4e3eab..3b2930f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt
@@ -18,6 +18,7 @@
 
 import android.os.PersistableBundle
 import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
+import android.telephony.CarrierConfigManager.KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT
 import android.telephony.CarrierConfigManager.KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL
 import androidx.annotation.VisibleForTesting
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -42,10 +43,11 @@
  * using the default config for logging purposes.
  *
  * NOTE to add new keys to be tracked:
- * 1. Define a new `private val` wrapping the key using [BooleanCarrierConfig]
- * 2. Define a public `val` exposing the wrapped flow using [BooleanCarrierConfig.config]
- * 3. Add the new [BooleanCarrierConfig] to the list of tracked configs, so they are properly
- *    updated when a new carrier config comes down
+ * 1. Define a new `private val` wrapping the key using [BooleanCarrierConfig] or [IntCarrierConfig]
+ * 2. Define a public `val` exposing the wrapped flow using [BooleanCarrierConfig.config] or
+ *    [IntCarrierConfig.config]
+ * 3. Add the new wrapped public flow to the list of tracked configs, so they are properly updated
+ *    when a new carrier config comes down
  */
 class SystemUiCarrierConfig
 internal constructor(
@@ -66,10 +68,16 @@
     /** Flow tracking the [KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL] config */
     val showOperatorNameInStatusBar: StateFlow<Boolean> = showOperatorName.config
 
+    private val satelliteHysteresisSeconds =
+        IntCarrierConfig(KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT, defaultConfig)
+    /** Flow tracking the [KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT] config */
+    val satelliteConnectionHysteresisSeconds: StateFlow<Int> = satelliteHysteresisSeconds.config
+
     private val trackedConfigs =
         listOf(
             inflateSignalStrength,
             showOperatorName,
+            satelliteHysteresisSeconds,
         )
 
     /** Ingest a new carrier config, and switch all of the tracked keys over to the new values */
@@ -90,15 +98,19 @@
     override fun toString(): String = trackedConfigs.joinToString { it.toString() }
 }
 
+interface CarrierConfig {
+    fun update(config: PersistableBundle)
+}
+
 /** Extracts [key] from the carrier config, and stores it in a flow */
 private class BooleanCarrierConfig(
     val key: String,
     defaultConfig: PersistableBundle,
-) {
+) : CarrierConfig {
     private val _configValue = MutableStateFlow(defaultConfig.getBoolean(key))
     val config = _configValue.asStateFlow()
 
-    fun update(config: PersistableBundle) {
+    override fun update(config: PersistableBundle) {
         _configValue.value = config.getBoolean(key)
     }
 
@@ -106,3 +118,20 @@
         return "$key=${config.value}"
     }
 }
+
+/** Extracts [key] from the carrier config, and stores it in a flow */
+private class IntCarrierConfig(
+    val key: String,
+    defaultConfig: PersistableBundle,
+) : CarrierConfig {
+    private val _configValue = MutableStateFlow(defaultConfig.getInt(key))
+    val config = _configValue.asStateFlow()
+
+    override fun update(config: PersistableBundle) {
+        _configValue.value = config.getInt(key)
+    }
+
+    override fun toString(): String {
+        return "$key=${config.value}"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 8f3b0e7..8f00b43 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -141,6 +141,9 @@
      */
     val hasPrioritizedNetworkCapabilities: StateFlow<Boolean>
 
+    /** Duration in seconds of the hysteresis to use when losing satellite connection. */
+    val satelliteConnectionHysteresisSeconds: StateFlow<Int>
+
     /**
      * True if this connection is in emergency callback mode.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
index 3cb138bd..af34a57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
@@ -206,6 +206,8 @@
 
     override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false)
 
+    override val satelliteConnectionHysteresisSeconds = MutableStateFlow(0)
+
     override suspend fun isInEcmMode(): Boolean = false
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
index f8858c5..2bc3bcb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
@@ -186,6 +186,9 @@
      */
     override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false).asStateFlow()
 
+    /** Non-applicable to carrier merged connections. */
+    override val satelliteConnectionHysteresisSeconds = MutableStateFlow(0).asStateFlow()
+
     override val dataEnabled: StateFlow<Boolean> = wifiRepository.isWifiEnabled
 
     override suspend fun isInEcmMode(): Boolean =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
index a124196..60b8599 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
@@ -334,6 +334,15 @@
                 activeRepo.value.hasPrioritizedNetworkCapabilities.value,
             )
 
+    override val satelliteConnectionHysteresisSeconds =
+        activeRepo
+            .flatMapLatest { it.satelliteConnectionHysteresisSeconds }
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                activeRepo.value.satelliteConnectionHysteresisSeconds.value
+            )
+
     override suspend fun isInEcmMode(): Boolean = activeRepo.value.isInEcmMode()
 
     class Factory
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index 77fd6be..f01ac0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -442,6 +442,9 @@
             .flowOn(bgDispatcher)
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
+    override val satelliteConnectionHysteresisSeconds: StateFlow<Int> =
+        systemUiCarrierConfig.satelliteConnectionHysteresisSeconds
+
     class Factory
     @Inject
     constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index adcf736..9d194cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -35,18 +35,25 @@
 import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
 import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.util.kotlin.pairwiseBy
+import kotlin.time.DurationUnit
+import kotlin.time.toDuration
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
 
 interface MobileIconInteractor {
     /** The table log created for this connection */
@@ -262,6 +269,43 @@
             MutableStateFlow(false).asStateFlow()
         }
 
+    private val hysteresisActive = MutableStateFlow(false)
+
+    private val isNonTerrestrialWithHysteresis: StateFlow<Boolean> =
+        combine(isNonTerrestrial, hysteresisActive) { isNonTerrestrial, hysteresisActive ->
+                if (hysteresisActive) {
+                    true
+                } else {
+                    isNonTerrestrial
+                }
+            }
+            .logDiffsForTable(
+                tableLogBuffer = tableLogBuffer,
+                columnName = "isNonTerrestrialWithHysteresis",
+                columnPrefix = "",
+                initialValue = Flags.carrierEnabledSatelliteFlag(),
+            )
+            .stateIn(scope, SharingStarted.Eagerly, Flags.carrierEnabledSatelliteFlag())
+
+    private val lostSatelliteConnection =
+        isNonTerrestrial.pairwiseBy { old, new -> hysteresisActive.value = old && !new }
+
+    init {
+        scope.launch { lostSatelliteConnection.collect() }
+        scope.launch {
+            hysteresisActive.collectLatest {
+                if (it) {
+                    delay(
+                        connectionRepository.satelliteConnectionHysteresisSeconds.value.toDuration(
+                            DurationUnit.SECONDS
+                        )
+                    )
+                    hysteresisActive.value = false
+                }
+            }
+        }
+    }
+
     override val isRoaming: StateFlow<Boolean> =
         combine(
                 connectionRepository.carrierNetworkChangeActive,
@@ -360,7 +404,7 @@
                 showExclamationMark.value,
                 carrierNetworkChangeActive.value,
             )
-        isNonTerrestrial
+        isNonTerrestrialWithHysteresis
             .flatMapLatest { ntn ->
                 if (ntn) {
                     satelliteIcon
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/DevicePostureControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java
index 422aa4d..de0eb49 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java
@@ -16,12 +16,13 @@
 
 package com.android.systemui.statusbar.policy;
 
+import android.annotation.NonNull;
 import android.content.Context;
+import android.hardware.devicestate.DeviceState;
 import android.hardware.devicestate.DeviceStateManager;
+import android.hardware.devicestate.DeviceStateUtil;
 import android.util.SparseIntArray;
 
-import androidx.annotation.NonNull;
-
 import com.android.app.tracing.ListenersTracing;
 import com.android.internal.R;
 import com.android.systemui.dagger.SysUISingleton;
@@ -42,8 +43,9 @@
     /** From androidx.window.common.COMMON_STATE_USE_BASE_STATE */
     private static final int COMMON_STATE_USE_BASE_STATE = 1000;
     private final List<Callback> mListeners = new ArrayList<>();
+    private final List<DeviceState> mSupportedStates;
+    private DeviceState mCurrentDeviceState;
     private int mCurrentDevicePosture = DEVICE_POSTURE_UNKNOWN;
-    private int mCurrentBasePosture = DEVICE_POSTURE_UNKNOWN;
 
     private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray();
 
@@ -76,21 +78,17 @@
             mDeviceStateToPostureMap.put(deviceState, posture);
         }
 
+        mSupportedStates = deviceStateManager.getSupportedDeviceStates();
         deviceStateManager.registerCallback(executor, new DeviceStateManager.DeviceStateCallback() {
             @Override
-            public void onStateChanged(int state) {
+            public void onDeviceStateChanged(@NonNull DeviceState state) {
+                mCurrentDeviceState = state;
                 Assert.isMainThread();
-                mCurrentDevicePosture =
-                        mDeviceStateToPostureMap.get(state, DEVICE_POSTURE_UNKNOWN);
-                sendUpdatePosture();
-            }
-
-            @Override
-            public void onBaseStateChanged(int state) {
-                Assert.isMainThread();
-                mCurrentBasePosture = mDeviceStateToPostureMap.get(state, DEVICE_POSTURE_UNKNOWN);
-
-                if (useBaseState()) {
+                int newDevicePosture =
+                        mDeviceStateToPostureMap.get(state.getIdentifier(), DEVICE_POSTURE_UNKNOWN);
+                if (newDevicePosture != mCurrentDevicePosture
+                        || newDevicePosture == COMMON_STATE_USE_BASE_STATE) {
+                    mCurrentDevicePosture = newDevicePosture;
                     sendUpdatePosture();
                 }
             }
@@ -120,7 +118,8 @@
     @Override
     public int getDevicePosture() {
         if (useBaseState()) {
-            return mCurrentBasePosture;
+            return DeviceStateUtil.calculateBaseStateIdentifier(mCurrentDeviceState,
+                    mSupportedStates);
         } else {
             return mCurrentDevicePosture;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
index 3008c866d..88cf46a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
@@ -20,6 +20,7 @@
 import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED;
 
 import android.annotation.Nullable;
+import android.hardware.devicestate.DeviceState;
 import android.hardware.devicestate.DeviceStateManager;
 import android.os.Trace;
 import android.util.IndentingPrintWriter;
@@ -120,18 +121,18 @@
         mDeviceStateRotationLockSettingsManager.updateSetting(deviceState, isRotationLocked);
     }
 
-    private void updateDeviceState(int state) {
-        mLogger.logUpdateDeviceState(mDeviceState, state);
-        if (Trace.isEnabled()) {
-            Trace.traceBegin(
-                    Trace.TRACE_TAG_APP, "updateDeviceState [state=" + state + "]");
-        }
+    private void updateDeviceState(@NonNull DeviceState state) {
+        mLogger.logUpdateDeviceState(mDeviceState, state.getIdentifier());
         try {
-            if (mDeviceState == state) {
+            if (Trace.isEnabled()) {
+                Trace.traceBegin(Trace.TRACE_TAG_APP,
+                        "updateDeviceState [state=" + state.getIdentifier() + "]");
+            }
+            if (mDeviceState == state.getIdentifier()) {
                 return;
             }
 
-            readPersistedSetting("updateDeviceState", state);
+            readPersistedSetting("updateDeviceState", state.getIdentifier());
         } finally {
             Trace.endSection();
         }
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/spatial/domain/SpatialAudioAvailabilityCriteria.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteria.kt
index 71bce5e..9d74c58 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteria.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteria.kt
@@ -18,11 +18,12 @@
 
 import com.android.systemui.volume.panel.component.spatial.domain.interactor.SpatialAudioComponentInteractor
 import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel
+import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
 import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.combine
 
 @VolumePanelScope
 class SpatialAudioAvailabilityCriteria
@@ -31,5 +32,11 @@
     ComponentAvailabilityCriteria {
 
     override fun isAvailable(): Flow<Boolean> =
-        interactor.isAvailable.map { it is SpatialAudioAvailabilityModel.SpatialAudio }
+        combine(interactor.isAvailable, interactor.isEnabled) { isAvailable, isEnabled ->
+            if (isAvailable is SpatialAudioAvailabilityModel.SpatialAudio) {
+                isEnabled !is SpatialAudioEnabledModel.Unknown
+            } else {
+                false
+            }
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
index 6032bfe..298ca67 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
@@ -18,8 +18,9 @@
 
 import android.media.AudioDeviceAttributes
 import android.media.AudioDeviceInfo
-import com.android.settingslib.bluetooth.CachedBluetoothDevice
 import com.android.settingslib.media.BluetoothMediaDevice
+import com.android.settingslib.media.MediaDevice
+import com.android.settingslib.media.PhoneMediaDevice
 import com.android.settingslib.media.domain.interactor.SpatializerInteractor
 import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
 import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel
@@ -54,12 +55,9 @@
     private val currentAudioDeviceAttributes: StateFlow<AudioDeviceAttributes?> =
         mediaOutputInteractor.currentConnectedDevice
             .map { mediaDevice ->
-                mediaDevice ?: return@map null
-                val btDevice: CachedBluetoothDevice =
-                    (mediaDevice as? BluetoothMediaDevice)?.cachedDevice ?: return@map null
-                btDevice.getAudioDeviceAttributes()
+                if (mediaDevice == null) builtinSpeaker else mediaDevice.getAudioDeviceAttributes()
             }
-            .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null)
+            .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), builtinSpeaker)
 
     /**
      * Returns spatial audio availability model. It can be:
@@ -115,7 +113,7 @@
             .stateIn(
                 coroutineScope,
                 SharingStarted.Eagerly,
-                SpatialAudioEnabledModel.Disabled,
+                SpatialAudioEnabledModel.Unknown,
             )
 
     /**
@@ -137,34 +135,50 @@
         changes.emit(Unit)
     }
 
-    private suspend fun CachedBluetoothDevice.getAudioDeviceAttributes(): AudioDeviceAttributes? {
-        return listOf(
-                AudioDeviceAttributes(
-                    AudioDeviceAttributes.ROLE_OUTPUT,
-                    AudioDeviceInfo.TYPE_BLE_HEADSET,
-                    address
-                ),
-                AudioDeviceAttributes(
-                    AudioDeviceAttributes.ROLE_OUTPUT,
-                    AudioDeviceInfo.TYPE_BLE_SPEAKER,
-                    address
-                ),
-                AudioDeviceAttributes(
-                    AudioDeviceAttributes.ROLE_OUTPUT,
-                    AudioDeviceInfo.TYPE_BLE_BROADCAST,
-                    address
-                ),
-                AudioDeviceAttributes(
-                    AudioDeviceAttributes.ROLE_OUTPUT,
-                    AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
-                    address
-                ),
-                AudioDeviceAttributes(
-                    AudioDeviceAttributes.ROLE_OUTPUT,
-                    AudioDeviceInfo.TYPE_HEARING_AID,
-                    address
-                )
+    private suspend fun MediaDevice.getAudioDeviceAttributes(): AudioDeviceAttributes? {
+        when (this) {
+            is PhoneMediaDevice -> return builtinSpeaker
+            is BluetoothMediaDevice -> {
+                val device = cachedDevice ?: return null
+                return listOf(
+                        AudioDeviceAttributes(
+                            AudioDeviceAttributes.ROLE_OUTPUT,
+                            AudioDeviceInfo.TYPE_BLE_HEADSET,
+                            device.address,
+                        ),
+                        AudioDeviceAttributes(
+                            AudioDeviceAttributes.ROLE_OUTPUT,
+                            AudioDeviceInfo.TYPE_BLE_SPEAKER,
+                            device.address,
+                        ),
+                        AudioDeviceAttributes(
+                            AudioDeviceAttributes.ROLE_OUTPUT,
+                            AudioDeviceInfo.TYPE_BLE_BROADCAST,
+                            device.address,
+                        ),
+                        AudioDeviceAttributes(
+                            AudioDeviceAttributes.ROLE_OUTPUT,
+                            AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
+                            device.address,
+                        ),
+                        AudioDeviceAttributes(
+                            AudioDeviceAttributes.ROLE_OUTPUT,
+                            AudioDeviceInfo.TYPE_HEARING_AID,
+                            device.address,
+                        )
+                    )
+                    .firstOrNull { spatializerInteractor.isSpatialAudioAvailable(it) }
+            }
+            else -> return null
+        }
+    }
+
+    private companion object {
+        val builtinSpeaker =
+            AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_OUTPUT,
+                AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
+                ""
             )
-            .firstOrNull { spatializerInteractor.isSpatialAudioAvailable(it) }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt
index 9735e5c..4255a40 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt
@@ -39,4 +39,7 @@
 
     /** Head tracking is enabled. This also means that [SpatialAudioEnabled]. */
     data object HeadTrackingEnabled : SpatialAudioEnabled
+
+    /** Spatial audio enabled state is unknown. */
+    data object Unknown : SpatialAudioEnabled
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt
index 0c91bbf..ecd89ea 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt
@@ -24,75 +24,24 @@
 class VolumeSliderInteractor @Inject constructor() {
 
     /** mimic percentage volume setting */
-    val displayValueRange: ClosedFloatingPointRange<Float> = 0f..100f
+    private val displayValueRange: ClosedFloatingPointRange<Float> = 0f..100f
 
     /**
      * Translates [volume], that belongs to [volumeRange] to the value that belongs to
      * [displayValueRange].
-     *
-     * [currentValue] is the raw value received from the slider. Returns [currentValue] when it
-     * translates to the same volume as [volume] parameter. This ensures smooth slider experience
-     * (avoids snapping when the user stops dragging).
      */
     fun processVolumeToValue(
         volume: Int,
         volumeRange: ClosedRange<Int>,
-        currentValue: Float?,
-        isMuted: Boolean,
     ): Float {
-        if (isMuted) {
-            return 0f
-        }
-        val changedVolume: Int? = currentValue?.let { translateValueToVolume(it, volumeRange) }
-        return if (volume != volumeRange.start && volume == changedVolume) {
-            currentValue
-        } else {
-            translateToRange(
-                currentValue = volume.toFloat(),
-                currentRangeStart = volumeRange.start.toFloat(),
-                currentRangeEnd = volumeRange.endInclusive.toFloat(),
-                targetRangeStart = displayValueRange.start,
-                targetRangeEnd = displayValueRange.endInclusive,
-            )
-        }
-    }
-
-    /** Translates [value] from [displayValueRange] to volume that has [volumeRange]. */
-    fun translateValueToVolume(
-        value: Float,
-        volumeRange: ClosedRange<Int>,
-    ): Int {
-        return translateToRange(
-                currentValue = value,
-                currentRangeStart = displayValueRange.start,
-                currentRangeEnd = displayValueRange.endInclusive,
-                targetRangeStart = volumeRange.start.toFloat(),
-                targetRangeEnd = volumeRange.endInclusive.toFloat(),
-            )
-            .toInt()
-    }
-
-    /**
-     * Translates a value from one range to another.
-     *
-     * ```
-     * Given: currentValue=3, currentRange=[0, 8], targetRange=[0, 100]
-     * Result: 37.5
-     * ```
-     */
-    private fun translateToRange(
-        currentValue: Float,
-        currentRangeStart: Float,
-        currentRangeEnd: Float,
-        targetRangeStart: Float,
-        targetRangeEnd: Float,
-    ): Float {
-        val currentRangeLength: Float = (currentRangeEnd - currentRangeStart)
-        val targetRangeLength: Float = targetRangeEnd - targetRangeStart
+        val currentRangeStart: Float = volumeRange.start.toFloat()
+        val targetRangeStart: Float = displayValueRange.start
+        val currentRangeLength: Float = (volumeRange.endInclusive.toFloat() - currentRangeStart)
+        val targetRangeLength: Float = displayValueRange.endInclusive - targetRangeStart
         if (currentRangeLength == 0f || targetRangeLength == 0f) {
             return 0f
         }
-        val volumeFraction: Float = (currentValue - currentRangeStart) / currentRangeLength
+        val volumeFraction: Float = (volume.toFloat() - currentRangeStart) / currentRangeLength
         return targetRangeStart + volumeFraction * targetRangeLength
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index 532e517..1b73208 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -28,6 +28,7 @@
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import kotlin.math.roundToInt
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
@@ -71,50 +72,49 @@
             AudioStream(AudioManager.STREAM_MUSIC) to R.string.stream_media_unavailable,
         )
 
-    private var value = 0f
     override val slider: StateFlow<SliderState> =
         combine(
                 audioVolumeInteractor.getAudioStream(audioStream),
                 audioVolumeInteractor.canChangeVolume(audioStream),
                 audioVolumeInteractor.ringerMode,
             ) { model, isEnabled, ringerMode ->
-                model.toState(value, isEnabled, ringerMode)
+                model.toState(isEnabled, ringerMode)
             }
             .stateIn(coroutineScope, SharingStarted.Eagerly, EmptyState)
 
-    override fun onValueChangeFinished(state: SliderState, newValue: Float) {
+    override fun onValueChanged(state: SliderState, newValue: Float) {
         val audioViewModel = state as? State
         audioViewModel ?: return
         coroutineScope.launch {
-            value = newValue
-            val volume =
-                volumeSliderInteractor.translateValueToVolume(
-                    newValue,
-                    audioViewModel.audioStreamModel.volumeRange
-                )
-            audioVolumeInteractor.setVolume(audioStream, volume)
+            audioVolumeInteractor.setVolume(audioStream, newValue.roundToInt())
+        }
+    }
+
+    override fun toggleMuted(state: SliderState) {
+        val audioViewModel = state as? State
+        audioViewModel ?: return
+        coroutineScope.launch {
+            audioVolumeInteractor.setMuted(audioStream, !audioViewModel.audioStreamModel.isMuted)
         }
     }
 
     private fun AudioStreamModel.toState(
-        value: Float,
         isEnabled: Boolean,
         ringerMode: RingerMode,
     ): State {
         return State(
-            value =
-                volumeSliderInteractor.processVolumeToValue(
-                    volume,
-                    volumeRange,
-                    value,
-                    isMuted,
+            value = volume.toFloat(),
+            valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(),
+            valueText =
+                SliderViewModel.formatValue(
+                    volumeSliderInteractor.processVolumeToValue(volume, volumeRange)
                 ),
-            valueRange = volumeSliderInteractor.displayValueRange,
             icon = getIcon(ringerMode),
             label = labelsByStream[audioStream]?.let(context::getString)
                     ?: error("No label for the stream: $audioStream"),
             disabledMessage = disabledTextByStream[audioStream]?.let(context::getString),
             isEnabled = isEnabled,
+            a11yStep = volumeRange.step,
             audioStreamModel = this,
         )
     }
@@ -156,8 +156,10 @@
         override val valueRange: ClosedFloatingPointRange<Float>,
         override val icon: Icon,
         override val label: String,
+        override val valueText: String,
         override val disabledMessage: String?,
         override val isEnabled: Boolean,
+        override val a11yStep: Int,
         val audioStreamModel: AudioStreamModel,
     ) : SliderState
 
@@ -165,8 +167,10 @@
         override val value: Float = 0f
         override val valueRange: ClosedFloatingPointRange<Float> = 0f..1f
         override val icon: Icon? = null
+        override val valueText: String = ""
         override val label: String = ""
         override val disabledMessage: String? = null
+        override val a11yStep: Int = 0
         override val isEnabled: Boolean = true
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
index ae93826..86b2d73 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
@@ -26,8 +26,8 @@
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import kotlin.math.roundToInt
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
@@ -46,46 +46,46 @@
 ) : SliderViewModel {
 
     private val volumeRange = 0..routingSession.routingSessionInfo.volumeMax
-    private val value = MutableStateFlow(0f)
 
     override val slider: StateFlow<SliderState> =
-        combine(value, mediaOutputInteractor.currentConnectedDevice) { value, _ ->
-                getCurrentState(value)
-            }
-            .stateIn(coroutineScope, SharingStarted.Eagerly, getCurrentState(value.value))
+        combine(mediaOutputInteractor.currentConnectedDevice) { _ -> getCurrentState() }
+            .stateIn(coroutineScope, SharingStarted.Eagerly, getCurrentState())
 
-    override fun onValueChangeFinished(state: SliderState, newValue: Float) {
+    override fun onValueChanged(state: SliderState, newValue: Float) {
         coroutineScope.launch {
-            value.value = newValue
-            castVolumeInteractor.setVolume(
-                routingSession,
-                volumeSliderInteractor.translateValueToVolume(newValue, volumeRange),
-            )
+            castVolumeInteractor.setVolume(routingSession, newValue.roundToInt())
         }
     }
 
-    private fun getCurrentState(value: Float): State {
-        return State(
-            value =
-                volumeSliderInteractor.processVolumeToValue(
-                    volume = routingSession.routingSessionInfo.volume,
-                    volumeRange = volumeRange,
-                    currentValue = value,
-                    isMuted = false,
-                ),
-            valueRange = volumeSliderInteractor.displayValueRange,
+    override fun toggleMuted(state: SliderState) {
+        // do nothing because this action isn't supported for Cast sliders.
+    }
+
+    private fun getCurrentState(): State =
+        State(
+            value = routingSession.routingSessionInfo.volume.toFloat(),
+            valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(),
             icon = Icon.Resource(R.drawable.ic_cast, null),
+            valueText =
+                SliderViewModel.formatValue(
+                    volumeSliderInteractor.processVolumeToValue(
+                        volume = routingSession.routingSessionInfo.volume,
+                        volumeRange = volumeRange,
+                    )
+                ),
             label = context.getString(R.string.media_device_cast),
             isEnabled = true,
+            a11yStep = 1
         )
-    }
 
     private data class State(
         override val value: Float,
         override val valueRange: ClosedFloatingPointRange<Float>,
         override val icon: Icon,
+        override val valueText: String,
         override val label: String,
         override val isEnabled: Boolean,
+        override val a11yStep: Int,
     ) : SliderState {
         override val disabledMessage: String?
             get() = null
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
index 6e9794b..b87d0a7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
@@ -27,7 +27,13 @@
     val value: Float
     val valueRange: ClosedFloatingPointRange<Float>
     val icon: Icon?
-    val label: String
-    val disabledMessage: String?
     val isEnabled: Boolean
+    val valueText: String
+    val label: String
+    /**
+     * A11y slider controls works by adjusting one step up or down. The default slider step isn't
+     * enough to trigger rounding to the correct value.
+     */
+    val a11yStep: Int
+    val disabledMessage: String?
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
index 0c4b322..e78f833 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
@@ -23,5 +23,12 @@
 
     val slider: StateFlow<SliderState>
 
-    fun onValueChangeFinished(state: SliderState, newValue: Float)
+    fun onValueChanged(state: SliderState, newValue: Float)
+
+    fun toggleMuted(state: SliderState)
+
+    companion object {
+
+        fun formatValue(value: Float): String = "%.0f".format(value)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt
index 7fd9c8a..635191a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt
@@ -46,12 +46,18 @@
                     !footerComponents.contains(it.key) &&
                     it.key != bottomBar
             }
-        val headerComponents = components.filter { headerComponents.contains(it.key) }
-        val footerComponents = components.filter { footerComponents.contains(it.key) }
+        val headerComponents =
+            components
+                .filter { it.key in headerComponents }
+                .sortedBy { headerComponents.indexOf(it.key) }
+        val footerComponents =
+            components
+                .filter { it.key in footerComponents }
+                .sortedBy { footerComponents.indexOf(it.key) }
         return ComponentsLayout(
-            headerComponents = headerComponents.sortedBy { it.key },
+            headerComponents = headerComponents,
             contentComponents = contentComponents.sortedBy { it.key },
-            footerComponents = footerComponents.sortedBy { it.key },
+            footerComponents = footerComponents,
             bottomBarComponent = components.find { it.key == bottomBar }
                     ?: error(
                         "VolumePanelComponents.BOTTOM_BAR must be present in the default " +
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 7674fe9..c294bd8 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -21,6 +21,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
@@ -259,7 +260,19 @@
             public void moveFocusedTaskToFullscreen(int displayId) {
                 splitScreen.goToFullscreenFromSplit();
             }
+
+            @Override
+            public void setSplitscreenFocus(boolean leftOrTop) {
+                splitScreen.setSplitscreenFocus(leftOrTop);
+            }
         });
+        splitScreen.registerSplitAnimationListener(new SplitScreen.SplitInvocationListener() {
+            @Override
+            public void onSplitAnimationInvoked(boolean animationRunning) {
+                mSysUiState.setFlag(SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION, animationRunning)
+                        .commitUpdate(mDisplayTracker.getDefaultDisplayId());
+            }
+        }, mSysUiMainExecutor);
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 5882b56..572a6c1 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -113,7 +113,7 @@
             android:excludeFromRecents="true"
             />
 
-        <activity android:name="com.android.systemui.screenshot.ScrollViewActivity"
+        <activity android:name="com.android.systemui.screenshot.scroll.ScrollViewActivity"
                   android:exported="false" />
 
         <activity android:name="com.android.systemui.screenshot.RecyclerViewActivity"
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
index 90587d7..f1dfdf4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
@@ -20,16 +20,18 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
 import android.view.View;
 import android.view.ViewTreeObserver;
 import android.widget.FrameLayout;
 
-import com.android.internal.jank.InteractionJankMonitor;
 import com.android.keyguard.logging.KeyguardLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.power.data.repository.FakePowerRepository;
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
 import com.android.systemui.res.R;
@@ -46,6 +48,7 @@
 
 public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase {
 
+    private KosmosJavaAdapter mKosmos;
     @Mock protected KeyguardStatusView mKeyguardStatusView;
 
     @Mock protected KeyguardSliceViewController mKeyguardSliceViewController;
@@ -57,7 +60,6 @@
     @Mock protected ScreenOffAnimationController mScreenOffAnimationController;
     @Mock protected KeyguardLogger mKeyguardLogger;
     @Mock protected KeyguardStatusViewController mControllerMock;
-    @Mock protected InteractionJankMonitor mInteractionJankMonitor;
     @Mock protected ViewTreeObserver mViewTreeObserver;
     @Mock protected DumpManager mDumpManager;
     protected FakeKeyguardRepository mFakeKeyguardRepository;
@@ -71,6 +73,7 @@
 
     @Before
     public void setup() {
+        mKosmos = new KosmosJavaAdapter(this);
         MockitoAnnotations.initMocks(this);
 
         KeyguardInteractorFactory.WithDependencies deps = KeyguardInteractorFactory.create();
@@ -87,7 +90,7 @@
                 mDozeParameters,
                 mScreenOffAnimationController,
                 mKeyguardLogger,
-                mInteractionJankMonitor,
+                mKosmos.getInteractionJankMonitor(),
                 deps.getKeyguardInteractor(),
                 mDumpManager,
                 PowerInteractorFactory.create(
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index e893eb1..11fe862 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -30,12 +30,14 @@
 import static org.mockito.Mockito.when;
 
 import android.animation.AnimatorTestRule;
+import android.platform.test.annotations.DisableFlags;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.View;
 
 import com.android.app.animation.Interpolators;
+import com.android.systemui.Flags;
 import com.android.systemui.animation.ViewHierarchyAnimator;
 import com.android.systemui.plugins.clocks.ClockConfig;
 import com.android.systemui.plugins.clocks.ClockController;
@@ -80,6 +82,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void onLocaleListChangedNotifiesClockSwitchController() {
         ArgumentCaptor<ConfigurationListener> configurationListenerArgumentCaptor =
                 ArgumentCaptor.forClass(ConfigurationListener.class);
@@ -239,6 +242,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void statusAreaHeightChange_animatesHeightOutputChange() {
         // Init & Capture Layout Listener
         mController.onInit();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 336a97e..fde45d3 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -135,6 +135,7 @@
 import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus;
 import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.UserTracker;
@@ -195,7 +196,7 @@
             DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", false, TEST_GROUP_UUID,
             TEST_CARRIER_ID, PROFILE_CLASS_PROVISIONING);
     private static final int FINGERPRINT_SENSOR_ID = 1;
-
+    private KosmosJavaAdapter mKosmos;
     @Mock
     private UserTracker mUserTracker;
     @Mock
@@ -240,7 +241,6 @@
     private AuthController mAuthController;
     @Mock
     private TelephonyListenerManager mTelephonyListenerManager;
-    @Mock
     private InteractionJankMonitor mInteractionJankMonitor;
     @Mock
     private LatencyTracker mLatencyTracker;
@@ -300,6 +300,8 @@
 
     @Before
     public void setup() throws RemoteException {
+        mKosmos = new KosmosJavaAdapter(this);
+        mInteractionJankMonitor = mKosmos.getInteractionJankMonitor();
         MockitoAnnotations.initMocks(this);
         when(mSessionTracker.getSessionId(SESSION_KEYGUARD)).thenReturn(mKeyguardInstanceId);
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
index d742da7..1c77351 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -147,6 +147,7 @@
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
 
         mSetFlagsRule.disableFlags(Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR);
+        mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
 
         mFeatureFlags = new FakeFeatureFlags();
         mFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
index 976cd5bd..1f7d033 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
@@ -90,12 +90,12 @@
         mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance,
                 mSecureSettings));
         mMenuView.setTranslationY(halfScreenHeight);
-        doNothing().when(mMenuView).gotoEditScreen();
 
         mMenuViewLayer = spy(new MenuViewLayer(
                 mContext, stubWindowManager, mAccessibilityManager,
                 stubMenuViewModel, stubMenuViewAppearance, mMenuView,
                 mock(IAccessibilityFloatingMenu.class), mSecureSettings));
+        doNothing().when(mMenuViewLayer).gotoEditScreen();
 
         doReturn(mDraggableBounds).when(mMenuView).getMenuDraggableBounds();
         mStubListView = new RecyclerView(mContext);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index de795a7..de696f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -35,6 +35,7 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -49,6 +50,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.graphics.Insets;
@@ -136,6 +138,8 @@
     private WindowManager mStubWindowManager;
     @Mock
     private AccessibilityManager mStubAccessibilityManager;
+    @Mock
+    private PackageManager mMockPackageManager;
     private final SecureSettings mSecureSettings = TestUtils.mockSecureSettings();
 
     private final NotificationManager mMockNotificationManager = mock(NotificationManager.class);
@@ -162,14 +166,15 @@
                 new MenuView(mSpyContext, mMenuViewModel, menuViewAppearance, mSecureSettings));
         // Ensure tests don't actually update metrics.
         doNothing().when(mMenuView).incrementTexMetric(any(), anyInt());
-        doNothing().when(mMenuView).gotoEditScreen();
 
         mMenuViewLayer = spy(new MenuViewLayer(mSpyContext, mStubWindowManager,
                 mStubAccessibilityManager, mMenuViewModel, menuViewAppearance, mMenuView,
                 mFloatingMenu, mSecureSettings));
-        mMenuView = (MenuView) mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW);
         mMenuAnimationController = mMenuView.getMenuAnimationController();
 
+        doNothing().when(mSpyContext).startActivity(any());
+        when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager);
+
         mLastAccessibilityButtonTargets =
                 Settings.Secure.getStringForUser(mSpyContext.getContentResolver(),
                         Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT);
@@ -277,9 +282,31 @@
 
     @Test
     @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
-    public void onEditAction_gotoEditScreen_isCalled() {
+    public void onEditAction_startsActivity() {
+        mockActivityQuery(true);
         mMenuViewLayer.dispatchAccessibilityAction(R.id.action_edit);
-        verify(mMenuView).gotoEditScreen();
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mSpyContext).startActivity(intentCaptor.capture());
+        assertThat(intentCaptor.getValue().getAction()).isEqualTo(
+                mMenuViewLayer.getIntentForEditScreen().getAction());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+    public void onEditAction_noResolve_doesNotStart() {
+        mockActivityQuery(false);
+        mMenuViewLayer.dispatchAccessibilityAction(R.id.action_edit);
+        verify(mSpyContext, never()).startActivity(any());
+    }
+
+    @Test
+    public void getIntentForEditScreen_validate() {
+        Intent intent = mMenuViewLayer.getIntentForEditScreen();
+        String[] targets = intent.getBundleExtra(
+                ":settings:show_fragment_args").getStringArray("targets");
+
+        assertThat(intent.getAction()).isEqualTo(Settings.ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS);
+        assertThat(targets).asList().containsExactlyElementsIn(TestUtils.TEST_BUTTON_TARGETS);
     }
 
     @Test
@@ -308,20 +335,6 @@
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
-    public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme_old() {
-        mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 100));
-        final PointF beforePosition = mMenuView.getMenuPosition();
-
-        dispatchShowingImeInsets();
-
-        final float menuBottom = mMenuView.getTranslationY() + mMenuView.getMenuHeight();
-        assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x);
-        assertThat(menuBottom).isLessThan(beforePosition.y);
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
     public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme() {
         mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 100));
         final PointF beforePosition = mMenuView.getMenuPosition();
@@ -337,19 +350,6 @@
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
-    public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition_old() {
-        mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 200));
-        final PointF beforePosition = mMenuView.getMenuPosition();
-
-        dispatchHidingImeInsets();
-
-        assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x);
-        assertThat(mMenuView.getTranslationY()).isEqualTo(beforePosition.y);
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
     public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition() {
         mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 200));
         final PointF beforePosition = mMenuView.getMenuPosition();
@@ -527,4 +527,15 @@
         magnetListener.onReleasedInTarget(
                 new MagnetizedObject.MagneticTarget(view, 200), mock(MagnetizedObject.class));
     }
+
+    private void mockActivityQuery(boolean successfulQuery) {
+        // Query just needs to return a non-empty set to be successful.
+        ArrayList<ResolveInfo> resolveInfos = new ArrayList<>();
+        if (successfulQuery) {
+            resolveInfos.add(new ResolveInfo());
+        }
+        when(mMockPackageManager.queryIntentActivities(
+                any(), any(PackageManager.ResolveInfoFlags.class))).thenReturn(resolveInfos);
+    }
+
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
index eced465..f6288b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
@@ -22,19 +22,13 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 import android.app.UiModeManager;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.graphics.Rect;
 import android.graphics.drawable.GradientDrawable;
 import android.platform.test.annotations.EnableFlags;
-import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.WindowManager;
@@ -58,8 +52,6 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
-import java.util.ArrayList;
-
 /** Tests for {@link MenuView}. */
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -79,8 +71,6 @@
     private AccessibilityManager mAccessibilityManager;
 
     private SysuiTestableContext mSpyContext;
-    @Mock
-    private PackageManager mMockPackageManager;
 
     @Before
     public void setUp() throws Exception {
@@ -91,7 +81,6 @@
         mSpyContext = spy(mContext);
         doNothing().when(mSpyContext).startActivity(any());
 
-        when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager);
         final SecureSettings secureSettings = TestUtils.mockSecureSettings();
         final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
                 secureSettings);
@@ -178,32 +167,6 @@
         assertThat(radiiAnimator.isStarted()).isTrue();
     }
 
-    @Test
-    public void getIntentForEditScreen_validate() {
-        Intent intent = mMenuView.getIntentForEditScreen();
-        String[] targets = intent.getBundleExtra(
-                ":settings:show_fragment_args").getStringArray("targets");
-
-        assertThat(intent.getAction()).isEqualTo(Settings.ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS);
-        assertThat(targets).asList().containsExactlyElementsIn(TestUtils.TEST_BUTTON_TARGETS);
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
-    public void gotoEditScreen_sendsIntent() {
-        mockActivityQuery(true);
-        mMenuView.gotoEditScreen();
-        verify(mSpyContext).startActivity(any());
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
-    public void gotoEditScreen_noResolve_doesNotStart() {
-        mockActivityQuery(false);
-        mMenuView.gotoEditScreen();
-        verify(mSpyContext, never()).startActivity(any());
-    }
-
     private InstantInsetLayerDrawable getMenuViewInsetLayer() {
         return (InstantInsetLayerDrawable) mMenuView.getBackground();
     }
@@ -226,14 +189,4 @@
         mUiModeManager.setNightMode(mNightMode);
         Prefs.putString(mContext, Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, mLastPosition);
     }
-
-    private void mockActivityQuery(boolean successfulQuery) {
-        // Query just needs to return a non-empty set to be successful.
-        ArrayList<ResolveInfo> resolveInfos = new ArrayList<>();
-        if (successfulQuery) {
-            resolveInfos.add(new ResolveInfo());
-        }
-        when(mMockPackageManager.queryIntentActivities(
-                any(), any(PackageManager.ResolveInfoFlags.class))).thenReturn(resolveInfos);
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt
index 96ce3ab..b73e4e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt
@@ -18,6 +18,8 @@
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.policy.DecorView
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.jank.interactionJankMonitor
+import com.android.systemui.kosmos.Kosmos
 import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertFalse
@@ -31,7 +33,6 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
 import org.mockito.Mockito.any
 import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
@@ -43,7 +44,7 @@
     private lateinit var mDialogTransitionAnimator: DialogTransitionAnimator
     private val attachedViews = mutableSetOf<View>()
 
-    @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
+    val interactionJankMonitor = Kosmos().interactionJankMonitor
     @get:Rule val rule = MockitoJUnit.rule()
 
     @Before
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..072569d 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
@@ -40,6 +39,7 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.Flags.FLAG_CONSTRAINT_BP
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
 import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
@@ -376,6 +376,7 @@
 
     @Test
     fun testShowBiometricUI() {
+        mSetFlagsRule.disableFlags(FLAG_CONSTRAINT_BP)
         val container = initializeFingerprintContainer()
 
         waitForIdleSync()
@@ -386,7 +387,6 @@
 
     @Test
     fun testShowCredentialUI_withDescription() {
-        mSetFlagsRule.disableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
         val container = initializeFingerprintContainer(
                 authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
         )
@@ -397,7 +397,9 @@
     }
 
     @Test
+    @Ignore("b/302735104")
     fun testShowCredentialUI_withCustomBp() {
+        mSetFlagsRule.disableFlags(FLAG_CONSTRAINT_BP)
         val container = initializeFingerprintContainer(
                 authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL,
                 isUsingContentView = true
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/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/display/data/repository/DeviceStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DeviceStateRepositoryTest.kt
index 21b8aca..c79cbab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DeviceStateRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DeviceStateRepositoryTest.kt
@@ -85,7 +85,9 @@
         testScope.runTest {
             val state = displayState()
 
-            deviceStateManagerListener.value.onStateChanged(TEST_FOLDED)
+            deviceStateManagerListener.value.onDeviceStateChanged(
+                getDeviceStateForIdentifier(TEST_FOLDED)
+            )
 
             assertThat(state()).isEqualTo(DeviceState.FOLDED)
         }
@@ -95,7 +97,9 @@
         testScope.runTest {
             val state = displayState()
 
-            deviceStateManagerListener.value.onStateChanged(TEST_HALF_FOLDED)
+            deviceStateManagerListener.value.onDeviceStateChanged(
+                getDeviceStateForIdentifier(TEST_HALF_FOLDED)
+            )
 
             assertThat(state()).isEqualTo(DeviceState.HALF_FOLDED)
         }
@@ -105,7 +109,9 @@
         testScope.runTest {
             val state = displayState()
 
-            deviceStateManagerListener.value.onStateChanged(TEST_UNFOLDED)
+            deviceStateManagerListener.value.onDeviceStateChanged(
+                getDeviceStateForIdentifier(TEST_UNFOLDED)
+            )
 
             assertThat(state()).isEqualTo(DeviceState.UNFOLDED)
         }
@@ -115,7 +121,9 @@
         testScope.runTest {
             val state = displayState()
 
-            deviceStateManagerListener.value.onStateChanged(TEST_REAR_DISPLAY)
+            deviceStateManagerListener.value.onDeviceStateChanged(
+                getDeviceStateForIdentifier(TEST_REAR_DISPLAY)
+            )
 
             assertThat(state()).isEqualTo(DeviceState.REAR_DISPLAY)
         }
@@ -125,7 +133,9 @@
         testScope.runTest {
             val state = displayState()
 
-            deviceStateManagerListener.value.onStateChanged(TEST_CONCURRENT_DISPLAY)
+            deviceStateManagerListener.value.onDeviceStateChanged(
+                getDeviceStateForIdentifier(TEST_CONCURRENT_DISPLAY)
+            )
 
             assertThat(state()).isEqualTo(DeviceState.CONCURRENT_DISPLAY)
         }
@@ -135,7 +145,9 @@
         testScope.runTest {
             val state = displayState()
 
-            deviceStateManagerListener.value.onStateChanged(123456)
+            deviceStateManagerListener.value.onDeviceStateChanged(
+                getDeviceStateForIdentifier(123456)
+            )
 
             assertThat(state()).isEqualTo(DeviceState.UNKNOWN)
         }
@@ -152,6 +164,13 @@
 
     private fun Int.toIntArray() = listOf(this).toIntArray()
 
+    private fun getDeviceStateForIdentifier(id: Int): android.hardware.devicestate.DeviceState {
+        return android.hardware.devicestate.DeviceState(
+            android.hardware.devicestate.DeviceState.Configuration.Builder(id, /* name= */ "")
+                .build()
+        )
+    }
+
     private companion object {
         // Used to fake the ids in the test. Note that there is no guarantees different devices will
         // have the same ids (that's why the ones in this test start from 41)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 1849245..272b488 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -75,7 +75,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.foldables.FoldGracePeriodProvider;
-import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.InstanceId;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.widget.LockPatternUtils;
@@ -181,7 +180,6 @@
     private @Mock NotificationShadeDepthController mNotificationShadeDepthController;
     private @Mock KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     private @Mock ScreenOffAnimationController mScreenOffAnimationController;
-    private @Mock InteractionJankMonitor mInteractionJankMonitor;
     private @Mock ScreenOnCoordinator mScreenOnCoordinator;
     private @Mock KeyguardTransitions mKeyguardTransitions;
     private @Mock ShadeController mShadeController;
@@ -235,8 +233,6 @@
         when(mLockPatternUtils.getDevicePolicyManager()).thenReturn(mDevicePolicyManager);
         when(mPowerManager.newWakeLock(anyInt(), any())).thenReturn(mock(WakeLock.class));
         when(mPowerManager.isInteractive()).thenReturn(true);
-        when(mInteractionJankMonitor.begin(any(), anyInt())).thenReturn(true);
-        when(mInteractionJankMonitor.end(anyInt())).thenReturn(true);
         mContext.addMockSystemService(Context.ALARM_SERVICE, mAlarmManager);
         final ViewRootImpl testViewRoot = mock(ViewRootImpl.class);
         when(testViewRoot.getView()).thenReturn(mock(View.class));
@@ -1245,7 +1241,7 @@
                 () -> mNotificationShadeDepthController,
                 mScreenOnCoordinator,
                 mKeyguardTransitions,
-                mInteractionJankMonitor,
+                mKosmos.getInteractionJankMonitor(),
                 mDreamOverlayStateController,
                 mJavaAdapter,
                 mWallpaperRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index ad80a06..8700001 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -27,7 +27,9 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.keyguard.shared.model.TransitionState
@@ -37,6 +39,7 @@
 import java.math.BigDecimal
 import java.math.RoundingMode
 import java.util.UUID
+import kotlinx.coroutines.flow.dropWhile
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.test.TestScope
@@ -224,6 +227,101 @@
         }
 
     @Test
+    fun startingSecondManualTransitionWillCancelPreviousManualTransition() =
+        TestScope().runTest {
+            // Drop initial steps from OFF which are sent in the constructor
+            val steps = mutableListOf<TransitionStep>()
+            val job =
+                underTest.transitions
+                    .dropWhile { step -> step.from == OFF }
+                    .onEach { steps.add(it) }
+                    .launchIn(this)
+
+            val firstUuid =
+                underTest.startTransition(
+                    TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator = null)
+                )
+            runCurrent()
+
+            checkNotNull(firstUuid)
+            underTest.updateTransition(firstUuid, 0.5f, TransitionState.RUNNING)
+            runCurrent()
+
+            val secondUuid =
+                underTest.startTransition(
+                    TransitionInfo(OWNER_NAME, AOD, DREAMING, animator = null)
+                )
+            runCurrent()
+
+            checkNotNull(secondUuid)
+            underTest.updateTransition(secondUuid, 0.7f, TransitionState.RUNNING)
+            // Trying to transition the old uuid should be ignored.
+            underTest.updateTransition(firstUuid, 0.6f, TransitionState.RUNNING)
+            runCurrent()
+
+            assertThat(steps)
+                .containsExactly(
+                    TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME),
+                    TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.RUNNING, OWNER_NAME),
+                    TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.CANCELED, OWNER_NAME),
+                    TransitionStep(AOD, DREAMING, 0.5f, TransitionState.STARTED, OWNER_NAME),
+                    TransitionStep(AOD, DREAMING, 0.7f, TransitionState.RUNNING, OWNER_NAME),
+                )
+                .inOrder()
+
+            job.cancel()
+        }
+
+    @Test
+    fun startingSecondTransitionWillCancelPreviousManualTransition() =
+        TestScope().runTest {
+            // Drop initial steps from OFF which are sent in the constructor
+            val steps = mutableListOf<TransitionStep>()
+            val job =
+                underTest.transitions
+                    .dropWhile { step -> step.from == OFF }
+                    .onEach { steps.add(it) }
+                    .launchIn(this)
+
+            val uuid =
+                underTest.startTransition(
+                    TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator = null)
+                )
+            runCurrent()
+
+            checkNotNull(uuid)
+            underTest.updateTransition(uuid, 0.5f, TransitionState.RUNNING)
+            runCurrent()
+
+            // Start new transition to dreaming, should cancel previous one.
+            runner.startTransition(
+                this,
+                TransitionInfo(
+                    OWNER_NAME,
+                    AOD,
+                    DREAMING,
+                    getAnimator(),
+                    TransitionModeOnCanceled.RESET,
+                ),
+            )
+            runCurrent()
+
+            // Trying to transition the old uuid should be ignored.
+            underTest.updateTransition(uuid, 0.6f, TransitionState.RUNNING)
+            runCurrent()
+
+            assertThat(steps.take(3))
+                .containsExactly(
+                    TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME),
+                    TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.RUNNING, OWNER_NAME),
+                    TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.CANCELED, OWNER_NAME),
+                )
+                .inOrder()
+
+            job.cancel()
+        }
+
+    @Test
     fun attemptTomanuallyUpdateTransitionWithInvalidUUIDthrowsException() {
         underTest.updateTransition(UUID.randomUUID(), 0f, TransitionState.RUNNING)
         assertThat(wtfHandler.failed).isTrue()
@@ -336,6 +434,7 @@
 
     private class WtfHandler : TerribleFailureHandler {
         var failed = false
+
         override fun onTerribleFailure(tag: String, what: TerribleFailure, system: Boolean) {
             failed = true
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
index 09c56b0..58273d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
@@ -107,6 +107,7 @@
 
     @Test
     fun addViewsConditionally_migrateFlagOff() {
+        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
         mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
         mSetFlagsRule.disableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
         val constraintLayout = ConstraintLayout(context, null)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
index 87391cc..d410dac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
 import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
 import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -36,6 +37,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
+import org.mockito.Mockito.verify
 
 @ExperimentalCoroutinesApi
 @RunWith(JUnit4::class)
@@ -48,6 +50,20 @@
     private val underTest = kosmos.alternateBouncerViewModel
 
     @Test
+    fun showPrimaryBouncer() =
+        testScope.runTest {
+            underTest.showPrimaryBouncer()
+            verify(statusBarKeyguardViewManager).showPrimaryBouncer(any())
+        }
+
+    @Test
+    fun hideAlternateBouncer() =
+        testScope.runTest {
+            underTest.hideAlternateBouncer()
+            verify(statusBarKeyguardViewManager).hideAlternateBouncer(any())
+        }
+
+    @Test
     fun transitionToAlternateBouncer_scrimAlphaUpdate() =
         testScope.runTest {
             val scrimAlphas by collectValues(underTest.scrimAlpha)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
index d75553f..b593def 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
@@ -81,7 +81,7 @@
     @Test
     fun loadRecentTasks_singleTask_returnsTaskAsNotForeground() {
         givenRecentTasks(
-            createSingleTask(taskId = 1),
+            createSingleTask(taskId = 1, isVisible = true),
         )
 
         val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
@@ -90,10 +90,10 @@
     }
 
     @Test
-    fun loadRecentTasks_multipleTasks_returnsSecondTaskAsForegroundTask() {
+    fun loadRecentTasks_multipleTasks_returnsSecondVisibleTaskAsForegroundTask() {
         givenRecentTasks(
             createSingleTask(taskId = 1),
-            createSingleTask(taskId = 2),
+            createSingleTask(taskId = 2, isVisible = true),
             createSingleTask(taskId = 3),
         )
 
@@ -103,10 +103,25 @@
     }
 
     @Test
-    fun loadRecentTasks_secondTaskIsGrouped_marksBothGroupedTasksAsForeground() {
+    fun loadRecentTasks_multipleTasks_returnsSecondInvisibleTaskAsNotForegroundTask() {
         givenRecentTasks(
             createSingleTask(taskId = 1),
-            createTaskPair(taskId1 = 2, taskId2 = 3),
+            createSingleTask(taskId = 2, isVisible = false),
+            createSingleTask(taskId = 3),
+        )
+
+        val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
+
+        assertThat(result.map { it.isForegroundTask })
+            .containsExactly(false, false, false)
+            .inOrder()
+    }
+
+    @Test
+    fun loadRecentTasks_secondTaskIsGroupedAndVisible_marksBothGroupedTasksAsForeground() {
+        givenRecentTasks(
+            createSingleTask(taskId = 1),
+            createTaskPair(taskId1 = 2, taskId2 = 3, isVisible = true),
             createSingleTask(taskId = 4),
         )
 
@@ -117,6 +132,21 @@
             .inOrder()
     }
 
+    @Test
+    fun loadRecentTasks_secondTaskIsGroupedAndInvisible_marksBothGroupedTasksAsNotForeground() {
+        givenRecentTasks(
+            createSingleTask(taskId = 1),
+            createTaskPair(taskId1 = 2, taskId2 = 3, isVisible = false),
+            createSingleTask(taskId = 4),
+        )
+
+        val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
+
+        assertThat(result.map { it.isForegroundTask })
+            .containsExactly(false, false, false, false)
+            .inOrder()
+    }
+
     @Suppress("UNCHECKED_CAST")
     private fun givenRecentTasks(vararg tasks: GroupedRecentTaskInfo) {
         whenever(recentTasks.getRecentTasks(any(), any(), any(), any(), any())).thenAnswer {
@@ -136,11 +166,23 @@
             isForegroundTask = false,
         )
 
-    private fun createSingleTask(taskId: Int): GroupedRecentTaskInfo =
-        GroupedRecentTaskInfo.forSingleTask(createTaskInfo(taskId))
+    private fun createSingleTask(taskId: Int, isVisible: Boolean = false): GroupedRecentTaskInfo =
+        GroupedRecentTaskInfo.forSingleTask(createTaskInfo(taskId, isVisible))
 
-    private fun createTaskPair(taskId1: Int, taskId2: Int): GroupedRecentTaskInfo =
-        GroupedRecentTaskInfo.forSplitTasks(createTaskInfo(taskId1), createTaskInfo(taskId2), null)
+    private fun createTaskPair(
+        taskId1: Int,
+        taskId2: Int,
+        isVisible: Boolean = false
+    ): GroupedRecentTaskInfo =
+        GroupedRecentTaskInfo.forSplitTasks(
+            createTaskInfo(taskId1, isVisible),
+            createTaskInfo(taskId2, isVisible),
+            null
+        )
 
-    private fun createTaskInfo(taskId: Int) = RecentTaskInfo().apply { this.taskId = taskId }
+    private fun createTaskInfo(taskId: Int, isVisible: Boolean = false) =
+        RecentTaskInfo().apply {
+            this.taskId = taskId
+            this.isVisible = isVisible
+        }
 }
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/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
index a63b221..d1d9efc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
@@ -101,11 +101,12 @@
 import androidx.preference.PreferenceManager;
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.people.PeopleBackupFollowUpJob;
 import com.android.systemui.people.PeopleSpaceUtils;
 import com.android.systemui.people.SharedPreferencesHelper;
+import com.android.systemui.res.R;
+import com.android.systemui.settings.FakeUserTracker;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
 import com.android.systemui.statusbar.SbnBuilder;
@@ -265,6 +266,8 @@
 
     private final FakeExecutor mFakeExecutor = new FakeExecutor(mClock);
 
+    private final FakeUserTracker mUserTracker = new FakeUserTracker();
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -272,7 +275,7 @@
         mManager = new PeopleSpaceWidgetManager(mContext, mAppWidgetManager, mIPeopleManager,
                 mPeopleManager, mLauncherApps, mNotifCollection, mPackageManager,
                 Optional.of(mBubbles), mUserManager, mBackupManager, mINotificationManager,
-                mNotificationManager, mFakeExecutor);
+                mNotificationManager, mFakeExecutor, mUserTracker);
         mManager.attach(mListenerService);
 
         verify(mListenerService).addNotificationHandler(mListenerCaptor.capture());
@@ -1562,6 +1565,43 @@
                 String.valueOf(WIDGET_ID_WITH_KEY_IN_OPTIONS));
     }
 
+    @Test
+    public void testUpdateGeneratedPreview_flagDisabled() {
+        mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
+        mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle());
+        verify(mAppWidgetManager, times(0)).setWidgetPreview(any(), anyInt(), any());
+    }
+
+    @Test
+    public void testUpdateGeneratedPreview_userLocked() {
+        mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
+        when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(false);
+
+        mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle());
+        verify(mAppWidgetManager, times(0)).setWidgetPreview(any(), anyInt(), any());
+    }
+
+    @Test
+    public void testUpdateGeneratedPreview_userUnlocked() {
+        mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
+        when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(true);
+        when(mAppWidgetManager.setWidgetPreview(any(), anyInt(), any())).thenReturn(true);
+
+        mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle());
+        verify(mAppWidgetManager, times(1)).setWidgetPreview(any(), anyInt(), any());
+    }
+
+    @Test
+    public void testUpdateGeneratedPreview_doesNotSetTwice() {
+        mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
+        when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(true);
+        when(mAppWidgetManager.setWidgetPreview(any(), anyInt(), any())).thenReturn(true);
+
+        mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle());
+        mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle());
+        verify(mAppWidgetManager, times(1)).setWidgetPreview(any(), anyInt(), any());
+    }
+
     private void setFinalField(String fieldName, int value) {
         try {
             Field field = NotificationManager.Policy.class.getDeclaredField(fieldName);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
index 3122edb..761c411 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
 import com.android.systemui.recordissue.RecordIssueDialogDelegate
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserContextProvider
@@ -66,6 +67,7 @@
     @Mock private lateinit var keyguardDismissUtil: KeyguardDismissUtil
     @Mock private lateinit var keyguardStateController: KeyguardStateController
     @Mock private lateinit var dialogLauncherAnimator: DialogTransitionAnimator
+    @Mock private lateinit var panelInteractor: PanelInteractor
     @Mock private lateinit var userContextProvider: UserContextProvider
     @Mock private lateinit var delegateFactory: RecordIssueDialogDelegate.Factory
     @Mock private lateinit var dialogDelegate: RecordIssueDialogDelegate
@@ -96,6 +98,7 @@
                 keyguardDismissUtil,
                 keyguardStateController,
                 dialogLauncherAnimator,
+                panelInteractor,
                 userContextProvider,
                 delegateFactory,
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt
index 8986d99..cd1452a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt
@@ -68,7 +68,7 @@
     fun testGetValue_valueUnset() {
         testScope.runTest {
             userRepository.setSelectedUserInfo(SYSTEM_USER)
-            val actualValue by collectLastValue(bluetoothAutoOnRepository.getValue)
+            val actualValue by collectLastValue(bluetoothAutoOnRepository.isAutoOn)
 
             runCurrent()
 
@@ -81,7 +81,7 @@
     fun testGetValue_valueFalse() {
         testScope.runTest {
             userRepository.setSelectedUserInfo(SYSTEM_USER)
-            val actualValue by collectLastValue(bluetoothAutoOnRepository.getValue)
+            val actualValue by collectLastValue(bluetoothAutoOnRepository.isAutoOn)
 
             secureSettings.putIntForUser(SETTING_NAME, DISABLED, UserHandle.USER_SYSTEM)
             runCurrent()
@@ -94,7 +94,7 @@
     fun testGetValue_valueTrue() {
         testScope.runTest {
             userRepository.setSelectedUserInfo(SYSTEM_USER)
-            val actualValue by collectLastValue(bluetoothAutoOnRepository.getValue)
+            val actualValue by collectLastValue(bluetoothAutoOnRepository.isAutoOn)
 
             secureSettings.putIntForUser(SETTING_NAME, ENABLED, UserHandle.USER_SYSTEM)
             runCurrent()
@@ -104,15 +104,16 @@
     }
 
     @Test
-    fun testGetValue_valueTrue_secondaryUser_returnUnset() {
+    fun testGetValue_valueTrue_secondaryUser_returnTrue() {
         testScope.runTest {
             userRepository.setSelectedUserInfo(SECONDARY_USER)
-            val actualValue by collectLastValue(bluetoothAutoOnRepository.getValue)
+            val actualValue by collectLastValue(bluetoothAutoOnRepository.isAutoOn)
 
+            secureSettings.putIntForUser(SETTING_NAME, DISABLED, SYSTEM_USER_ID)
             secureSettings.putIntForUser(SETTING_NAME, ENABLED, SECONDARY_USER_ID)
             runCurrent()
 
-            assertThat(actualValue).isEqualTo(UNSET)
+            assertThat(actualValue).isEqualTo(ENABLED)
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
index dc211303..f88a5a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
@@ -19,7 +19,6 @@
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNotSame;
 
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.reset;
@@ -28,6 +27,7 @@
 
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.hardware.devicestate.DeviceState;
 import android.hardware.devicestate.DeviceStateManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -38,9 +38,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.res.R;
 import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.CommandQueue;
@@ -176,6 +174,6 @@
             DeviceStateManager.DeviceStateCallback {
 
         @Override
-        public void onStateChanged(int state) { }
+        public void onDeviceStateChanged(DeviceState state) { }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeSessionTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/FakeSessionTest.java
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeSessionTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/FakeSessionTest.java
index 4c8a4b0..aad46139 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeSessionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/FakeSessionTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 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,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
 
 import static com.google.common.util.concurrent.Futures.getUnchecked;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureClientTest.java
similarity index 93%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureClientTest.java
index 670a130..1023260 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureClientTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 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,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
 
 
 import static org.junit.Assert.assertEquals;
@@ -37,8 +37,8 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.screenshot.ScrollCaptureClient.CaptureResult;
-import com.android.systemui.screenshot.ScrollCaptureClient.Session;
+import com.android.systemui.screenshot.scroll.ScrollCaptureClient.CaptureResult;
+import com.android.systemui.screenshot.scroll.ScrollCaptureClient.Session;
 
 import com.google.common.util.concurrent.ListenableFuture;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureControllerTest.java
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureControllerTest.java
index 6f081c7..f39f543 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureControllerTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 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,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
 
 import static com.google.common.util.concurrent.Futures.getUnchecked;
 import static com.google.common.util.concurrent.Futures.immediateFuture;
@@ -36,7 +36,7 @@
 
 import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.screenshot.ScrollCaptureClient.Session;
+import com.android.systemui.screenshot.scroll.ScrollCaptureClient.Session;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureFrameworkSmokeTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureFrameworkSmokeTest.java
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureFrameworkSmokeTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureFrameworkSmokeTest.java
index de97bc3..5699cfc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureFrameworkSmokeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureFrameworkSmokeTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 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,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollViewActivity.java
similarity index 93%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java
rename to packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollViewActivity.java
index 4c84df2..04aba11 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollViewActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 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,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
 
 import android.app.Activity;
 import android.os.Bundle;
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/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index c31c625..1ee26db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -196,7 +196,8 @@
                 new ConfigurationInteractor(configurationRepository),
                 shadeRepository,
                 keyguardTransitionInteractor,
-                () -> sceneInteractor);
+                () -> sceneInteractor,
+                () -> mKosmos.getFromGoneTransitionInteractor());
         CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor();
 
         mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
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/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index a077164..b9451ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -17,7 +17,6 @@
 package com.android.systemui.shade;
 
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -223,7 +222,8 @@
                 new ConfigurationInteractor(configurationRepository),
                 mShadeRepository,
                 keyguardTransitionInteractor,
-                () -> sceneInteractor);
+                () -> sceneInteractor,
+                () -> mKosmos.getFromGoneTransitionInteractor());
 
         mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
         mFromPrimaryBouncerTransitionInteractor =
@@ -289,10 +289,6 @@
 
         when(mNotificationRemoteInputManager.isRemoteInputActive())
                 .thenReturn(false);
-        when(mInteractionJankMonitor.begin(any(), anyInt()))
-                .thenReturn(true);
-        when(mInteractionJankMonitor.end(anyInt()))
-                .thenReturn(true);
 
         when(mPanelView.getParent()).thenReturn(mPanelViewParent);
         when(mQs.getHeader()).thenReturn(mQsHeader);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
index 3defee9..722387c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
@@ -28,7 +28,7 @@
 import android.content.pm.ApplicationInfo;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
-
+import androidx.test.filters.FlakyTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.systemui.SysuiTestCase;
@@ -50,6 +50,7 @@
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Supplier;
 
+@FlakyTest(bugId = 327655994) // Also b/324682425
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class PluginInstanceTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index 103dcb7..dcd000a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -45,6 +45,7 @@
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.fromGoneTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.fromLockscreenTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.fromPrimaryBouncerTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
@@ -153,6 +154,7 @@
                 shadeRepository,
                 keyguardTransitionInteractor,
                 { kosmos.sceneInteractor },
+                { kosmos.fromGoneTransitionInteractor },
             )
 
         whenever(deviceEntryUdfpsInteractor.isUdfpsSupported).thenReturn(MutableStateFlow(false))
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/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
index f89b9cd..66a306e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
@@ -53,8 +53,6 @@
 import com.android.internal.logging.UiEventLogger.UiEventEnum
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.broadcast.FakeBroadcastDispatcher
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogcatEchoTracker
 import com.android.systemui.log.core.LogLevel
@@ -150,7 +148,10 @@
 
     @Before
     fun setUp() {
-        val user = UserInfo(ActivityManager.getCurrentUser(), "Current user", /* flags = */ 0)
+        val userId = ActivityManager.getCurrentUser()
+        val user = UserInfo(userId, "Current user", /* flags = */ 0)
+
+        deviceProvisionedController.currentUser = userId
         userTracker.set(listOf(user), /* currentUserIndex = */ 0)
 
         provider.start()
@@ -823,6 +824,13 @@
     }
 
     @Test
+    fun testShouldFsi_userSetupIncomplete() {
+        ensureUserSetupIncompleteFsiState()
+        assertShouldFsi(buildFsiEntry())
+        assertNoEventsLogged()
+    }
+
+    @Test
     fun testShouldNotFsi_noHunOrKeyguard() {
         ensureNoHunOrKeyguardFsiState()
         assertShouldNotFsi(buildFsiEntry())
@@ -888,7 +896,8 @@
         var statusBarState: Int? = null,
         var keyguardIsShowing: Boolean = false,
         var keyguardIsOccluded: Boolean = false,
-        var deviceProvisioned: Boolean = true
+        var deviceProvisioned: Boolean = true,
+        var currentUserSetup: Boolean = true
     )
 
     protected fun setState(state: State): Unit =
@@ -925,6 +934,7 @@
             keyguardStateController.isShowing = keyguardIsShowing
 
             deviceProvisionedController.deviceProvisioned = deviceProvisioned
+            deviceProvisionedController.isCurrentUserSetup = currentUserSetup
         }
 
     protected fun ensureState(block: State.() -> Unit) =
@@ -999,6 +1009,18 @@
         hunSettingEnabled = false
         keyguardIsShowing = false
         deviceProvisioned = false
+        currentUserSetup = true
+        run(block)
+    }
+
+    protected fun ensureUserSetupIncompleteFsiState(block: State.() -> Unit = {}) = ensureState {
+        isInteractive = true
+        isDreaming = false
+        statusBarState = SHADE
+        hunSettingEnabled = false
+        keyguardIsShowing = false
+        deviceProvisioned = true
+        currentUserSetup = false
         run(block)
     }
 
@@ -1009,6 +1031,7 @@
         hunSettingEnabled = false
         keyguardIsShowing = false
         deviceProvisioned = true
+        currentUserSetup = true
         run(block)
     }
 
@@ -1216,7 +1239,7 @@
 
                     neb.setImportance(importance)
                     val channel =
-                            NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance)
+                        NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance)
                     channel.isImportantConversation = isImportantConversation
                     neb.setChannel(channel)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/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/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index ea4ae17..dcbb93a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -49,6 +49,7 @@
 import com.android.systemui.shade.QuickSettingsController;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractorImpl;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -78,6 +79,7 @@
     @Mock private CommandQueue mCommandQueue;
     @Mock private QuickSettingsController mQuickSettingsController;
     @Mock private ShadeViewController mShadeViewController;
+    @Mock private PanelExpansionInteractorImpl mPanelExpansionInteractor;
     @Mock private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
     private final MetricsLogger mMetricsLogger = new FakeMetricsLogger();
     @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -112,6 +114,7 @@
                 mShadeController,
                 mCommandQueue,
                 mShadeViewController,
+                mPanelExpansionInteractor,
                 mRemoteInputQuickSettingsDisabler,
                 mMetricsLogger,
                 mKeyguardUpdateMonitor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index c8c54dbd..611cf91 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -49,6 +49,7 @@
 import android.app.trust.TrustManager;
 import android.content.BroadcastReceiver;
 import android.content.IntentFilter;
+import android.hardware.devicestate.DeviceState;
 import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.hardware.fingerprint.FingerprintManager;
@@ -1113,10 +1114,12 @@
     }
 
     private void setDeviceState(int state) {
+        DeviceState deviceState = new DeviceState(
+                new DeviceState.Configuration.Builder(state, "TEST").build());
         ArgumentCaptor<DeviceStateManager.DeviceStateCallback> callbackCaptor =
                 ArgumentCaptor.forClass(DeviceStateManager.DeviceStateCallback.class);
         verify(mDeviceStateManager).registerCallback(any(), callbackCaptor.capture());
-        callbackCaptor.getValue().onStateChanged(state);
+        callbackCaptor.getValue().onDeviceStateChanged(deviceState);
     }
 
     private void setGoToSleepStates(int... states) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt
index 649dc23..5d42d51 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.statusbar.phone
 
+import android.hardware.devicestate.DeviceState
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.internal.R
@@ -40,52 +41,52 @@
     @Before
     fun setUp() {
         initMocks(this)
-        setFoldedStates(DEVICE_STATE_FOLDED)
-        setGoToSleepStates(DEVICE_STATE_FOLDED)
+        setFoldedStates(DEVICE_STATE_FOLDED.identifier)
+        setGoToSleepStates(DEVICE_STATE_FOLDED.identifier)
         sut = FoldStateListener(mContext, listener)
     }
 
     @Test
     fun onStateChanged_stateFolded_notifiesWithFoldedAndGoingToSleep() {
-        sut.onStateChanged(DEVICE_STATE_FOLDED)
+        sut.onDeviceStateChanged(DEVICE_STATE_FOLDED)
 
         verify(listener).onFoldStateChanged(FOLDED, WILL_GO_TO_SLEEP)
     }
 
     @Test
     fun onStateChanged_stateHalfFolded_notifiesWithNotFoldedAndNotGoingToSleep() {
-        sut.onStateChanged(DEVICE_STATE_HALF_FOLDED)
+        sut.onDeviceStateChanged(DEVICE_STATE_HALF_FOLDED)
 
         verify(listener).onFoldStateChanged(NOT_FOLDED, WILL_NOT_SLEEP)
     }
 
     @Test
     fun onStateChanged_stateUnfolded_notifiesWithNotFoldedAndNotGoingToSleep() {
-        sut.onStateChanged(DEVICE_STATE_UNFOLDED)
+        sut.onDeviceStateChanged(DEVICE_STATE_UNFOLDED)
 
         verify(listener).onFoldStateChanged(NOT_FOLDED, WILL_NOT_SLEEP)
     }
 
     @Test
     fun onStateChanged_stateUnfoldedThenHalfFolded_notifiesOnce() {
-        sut.onStateChanged(DEVICE_STATE_UNFOLDED)
-        sut.onStateChanged(DEVICE_STATE_HALF_FOLDED)
+        sut.onDeviceStateChanged(DEVICE_STATE_UNFOLDED)
+        sut.onDeviceStateChanged(DEVICE_STATE_HALF_FOLDED)
 
         verify(listener, times(1)).onFoldStateChanged(NOT_FOLDED, WILL_NOT_SLEEP)
     }
 
     @Test
     fun onStateChanged_stateHalfFoldedThenUnfolded_notifiesOnce() {
-        sut.onStateChanged(DEVICE_STATE_HALF_FOLDED)
-        sut.onStateChanged(DEVICE_STATE_UNFOLDED)
+        sut.onDeviceStateChanged(DEVICE_STATE_HALF_FOLDED)
+        sut.onDeviceStateChanged(DEVICE_STATE_UNFOLDED)
 
         verify(listener, times(1)).onFoldStateChanged(NOT_FOLDED, WILL_NOT_SLEEP)
     }
 
     @Test
     fun onStateChanged_stateHalfFoldedThenFolded_notifiesTwice() {
-        sut.onStateChanged(DEVICE_STATE_HALF_FOLDED)
-        sut.onStateChanged(DEVICE_STATE_FOLDED)
+        sut.onDeviceStateChanged(DEVICE_STATE_HALF_FOLDED)
+        sut.onDeviceStateChanged(DEVICE_STATE_FOLDED)
 
         val inOrder = Mockito.inOrder(listener)
         inOrder.verify(listener).onFoldStateChanged(NOT_FOLDED, WILL_NOT_SLEEP)
@@ -94,8 +95,8 @@
 
     @Test
     fun onStateChanged_stateFoldedThenHalfFolded_notifiesTwice() {
-        sut.onStateChanged(DEVICE_STATE_FOLDED)
-        sut.onStateChanged(DEVICE_STATE_HALF_FOLDED)
+        sut.onDeviceStateChanged(DEVICE_STATE_FOLDED)
+        sut.onDeviceStateChanged(DEVICE_STATE_HALF_FOLDED)
 
         val inOrder = Mockito.inOrder(listener)
         inOrder.verify(listener).onFoldStateChanged(FOLDED, WILL_GO_TO_SLEEP)
@@ -117,9 +118,18 @@
     }
 
     companion object {
-        private const val DEVICE_STATE_FOLDED = 123
-        private const val DEVICE_STATE_HALF_FOLDED = 456
-        private const val DEVICE_STATE_UNFOLDED = 789
+        private val DEVICE_STATE_FOLDED = DeviceState(
+            DeviceState.Configuration.Builder(123 /* id */, "FOLDED" /* name */)
+                    .build()
+        )
+        private val DEVICE_STATE_HALF_FOLDED = DeviceState(
+            DeviceState.Configuration.Builder(456 /* id */, "HALF_FOLDED" /* name */)
+                    .build()
+        )
+        private val DEVICE_STATE_UNFOLDED = DeviceState(
+            DeviceState.Configuration.Builder(789 /* id */, "UNFOLDED" /* name */)
+                    .build()
+        )
 
         private const val FOLDED = true
         private const val NOT_FOLDED = false
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index 1748cff..d9e9c59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -177,7 +177,8 @@
                 new ConfigurationInteractor(new FakeConfigurationRepository()),
                 new FakeShadeRepository(),
                 keyguardTransitionInteractor,
-                () -> mKosmos.getSceneInteractor());
+                () -> mKosmos.getSceneInteractor(),
+                () -> mKosmos.getFromGoneTransitionInteractor());
         mViewModel =
                 new KeyguardStatusBarViewModel(
                         mTestScope.getBackgroundScope(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 443dd6a..1463680 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.shade.ShadeControllerImpl
 import com.android.systemui.shade.ShadeLogger
 import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.window.StatusBarWindowStateController
@@ -67,6 +68,7 @@
 class PhoneStatusBarViewControllerTest : SysuiTestCase() {
 
     @Mock private lateinit var shadeViewController: ShadeViewController
+    @Mock private lateinit var panelExpansionInteractor: PanelExpansionInteractor
     @Mock private lateinit var featureFlags: FeatureFlags
     @Mock private lateinit var moveFromCenterAnimation: StatusBarMoveFromCenterAnimationController
     @Mock private lateinit var sysuiUnfoldComponent: SysUIUnfoldComponent
@@ -195,7 +197,7 @@
     @Test
     fun handleTouchEventFromStatusBar_topEdgeTouch_viewNeverReceivesEvent() {
         `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
-        `when`(shadeViewController.isFullyCollapsed).thenReturn(true)
+        `when`(panelExpansionInteractor.isFullyCollapsed).thenReturn(true)
         val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
 
         view.onTouchEvent(event)
@@ -291,21 +293,22 @@
 
     private fun createAndInitController(view: PhoneStatusBarView): PhoneStatusBarViewController {
         return PhoneStatusBarViewController.Factory(
-                Optional.of(sysuiUnfoldComponent),
-                Optional.of(progressProvider),
-                featureFlags,
-                FakeSceneContainerFlags(),
-                userChipViewModel,
-                centralSurfacesImpl,
-                statusBarWindowStateController,
-                shadeControllerImpl,
-                shadeViewController,
-                windowRootView,
-                shadeLogger,
-                viewUtil,
-                configurationController,
-                mStatusOverlayHoverListenerFactory
-            )
+            Optional.of(sysuiUnfoldComponent),
+            Optional.of(progressProvider),
+            featureFlags,
+            FakeSceneContainerFlags(),
+            userChipViewModel,
+            centralSurfacesImpl,
+            statusBarWindowStateController,
+            shadeControllerImpl,
+            shadeViewController,
+            panelExpansionInteractor,
+            windowRootView,
+            shadeLogger,
+            viewUtil,
+            configurationController,
+            mStatusOverlayHoverListenerFactory
+        )
             .create(view)
             .also { it.init() }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 562aa6a..b0b9bec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -82,6 +82,9 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
+import com.android.systemui.keyguard.shared.model.KeyguardState;
+import com.android.systemui.keyguard.shared.model.TransitionState;
+import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.navigationbar.TaskbarDelegate;
 import com.android.systemui.plugins.ActivityStarter;
@@ -103,6 +106,8 @@
 
 import com.google.common.truth.Truth;
 
+import kotlin.Unit;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -1045,4 +1050,35 @@
         verify(mCentralSurfaces, never()).hideKeyguard();
         verify(mPrimaryBouncerInteractor, never()).show(true);
     }
+
+    @Test
+    public void altBouncerNotVisible_keyguardAuthenticatedBiometricsHandled() {
+        clearInvocations(mAlternateBouncerInteractor);
+        when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false);
+        mStatusBarKeyguardViewManager.consumeKeyguardAuthenticatedBiometricsHandled(Unit.INSTANCE);
+        verify(mAlternateBouncerInteractor, never()).hide();
+    }
+
+    @Test
+    public void altBouncerVisible_keyguardAuthenticatedBiometricsHandled() {
+        clearInvocations(mAlternateBouncerInteractor);
+        when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+        mStatusBarKeyguardViewManager.consumeKeyguardAuthenticatedBiometricsHandled(Unit.INSTANCE);
+        verify(mAlternateBouncerInteractor).hide();
+    }
+
+    @Test
+    public void fromAlternateBouncerTransitionStep() {
+        clearInvocations(mAlternateBouncerInteractor);
+        mStatusBarKeyguardViewManager.consumeFromAlternateBouncerTransitionSteps(
+                new TransitionStep(
+                        /* from */ KeyguardState.ALTERNATE_BOUNCER,
+                        /* to */ KeyguardState.GONE,
+                        /* value */ 1f,
+                        TransitionState.FINISHED,
+                        "StatusBarKeyguardViewManagerTest"
+                )
+        );
+        verify(mAlternateBouncerInteractor).hide();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 938b2f8..127a3d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -75,9 +75,9 @@
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeControllerImpl;
-import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.shade.data.repository.FakeShadeRepository;
 import com.android.systemui.shade.data.repository.ShadeAnimationRepository;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationClickNotifier;
@@ -257,7 +257,7 @@
                         new StatusBarNotificationActivityStarterLogger(logcatLogBuffer()),
                         mOnUserInteractionCallback,
                         mock(NotificationPresenter.class),
-                        mock(ShadeViewController.class),
+                        mock(PanelExpansionInteractor.class),
                         mock(NotificationShadeWindowController.class),
                         mActivityTransitionAnimator,
                         new ShadeAnimationInteractorLegacyImpl(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/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/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index 3bf54a3..57a89b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -100,10 +100,6 @@
                 handler = handler
         )
         controller.initialize(centralSurfaces, shadeViewController, lightRevealScrim)
-
-        // Screen off does not run if the panel is expanded, so we should say it's collapsed to test
-        // screen off.
-        `when`(shadeViewController.isFullyCollapsed).thenReturn(true)
     }
 
     @After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
index 1fb6e2c..c13e830 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.log.table.TableLogBufferFactory
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_EMERGENCY
@@ -679,6 +680,9 @@
         telephonyManager: TelephonyManager,
     ): MobileConnectionRepositoryImpl {
         whenever(telephonyManager.subscriptionId).thenReturn(SUB_ID)
+        val systemUiCarrierConfigMock: SystemUiCarrierConfig = mock()
+        whenever(systemUiCarrierConfigMock.satelliteConnectionHysteresisSeconds)
+            .thenReturn(MutableStateFlow(0))
 
         val realRepo =
             MobileConnectionRepositoryImpl(
@@ -689,7 +693,7 @@
                 SEP,
                 connectivityManager,
                 telephonyManager,
-                systemUiCarrierConfig = mock(),
+                systemUiCarrierConfig = systemUiCarrierConfigMock,
                 fakeBroadcastDispatcher,
                 mobileMappingsProxy = mock(),
                 testDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index 49953a1..c49fcf8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -43,12 +43,15 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.seconds
+import kotlin.time.DurationUnit
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 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.Test
@@ -675,6 +678,32 @@
             assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java)
         }
 
+    @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    @Test
+    fun satBasedIcon_hasHysteresisWhenDisabled() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.signalLevelIcon)
+
+            val hysteresisDuration = 5.seconds
+            connectionRepository.satelliteConnectionHysteresisSeconds.value =
+                hysteresisDuration.toInt(DurationUnit.SECONDS)
+
+            connectionRepository.isNonTerrestrial.value = true
+
+            assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java)
+
+            // Disable satellite
+            connectionRepository.isNonTerrestrial.value = false
+
+            // Satellite icon should still be visible
+            assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java)
+
+            // Wait for the icon to change
+            advanceTimeBy(hysteresisDuration)
+
+            assertThat(latest).isInstanceOf(SignalIconModel.Cellular::class.java)
+        }
+
     private fun createInteractor(
         overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl()
     ) =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt
index ce47170..c606511 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.policy
 
+import android.hardware.devicestate.DeviceState
 import android.hardware.devicestate.DeviceStateManager
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
@@ -30,6 +31,7 @@
 import com.android.systemui.statusbar.policy.DevicePostureController.SUPPORTED_POSTURES_SIZE
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -72,6 +74,18 @@
             com.android.internal.R.array.config_device_state_postures,
             deviceStateToPostureMapping
         )
+        whenever(deviceStateManager.supportedDeviceStates)
+            .thenReturn(
+                listOf(
+                    DEVICE_STATE_CLOSED,
+                    DEVICE_STATE_HALF_FOLDED,
+                    DEVICE_STATE_OPENED,
+                    DEVICE_STATE_FLIPPED,
+                    DEVICE_STATE_UNKNOWN,
+                    DEVICE_STATE_USE_BASE_STATE
+                )
+            )
+
         underTest =
             DevicePostureControllerImpl(
                 context,
@@ -86,20 +100,20 @@
         var posture = -1
         underTest.addCallback { posture = it }
 
-        deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_UNKNOWN)
-        assertThat(posture).isEqualTo(DEVICE_POSTURE_UNKNOWN)
-
-        deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_CLOSED)
+        deviceStateCallback.value.onDeviceStateChanged(DEVICE_STATE_CLOSED)
         assertThat(posture).isEqualTo(DEVICE_POSTURE_CLOSED)
 
-        deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_HALF_OPENED)
+        deviceStateCallback.value.onDeviceStateChanged(DEVICE_STATE_HALF_FOLDED)
         assertThat(posture).isEqualTo(DEVICE_POSTURE_HALF_OPENED)
 
-        deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_OPENED)
+        deviceStateCallback.value.onDeviceStateChanged(DEVICE_STATE_OPENED)
         assertThat(posture).isEqualTo(DEVICE_POSTURE_OPENED)
 
-        deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_FLIPPED)
+        deviceStateCallback.value.onDeviceStateChanged(DEVICE_STATE_FLIPPED)
         assertThat(posture).isEqualTo(DEVICE_POSTURE_FLIPPED)
+
+        deviceStateCallback.value.onDeviceStateChanged(DEVICE_STATE_UNKNOWN)
+        assertThat(posture).isEqualTo(DEVICE_POSTURE_UNKNOWN)
     }
 
     @Test
@@ -107,15 +121,26 @@
         var posture = -1
         underTest.addCallback { posture = it }
 
-        deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_HALF_OPENED)
+        deviceStateCallback.value.onDeviceStateChanged(DEVICE_STATE_HALF_FOLDED)
         assertThat(posture).isEqualTo(DEVICE_POSTURE_HALF_OPENED)
 
-        // base state change doesn't change the posture
-        deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_CLOSED)
+        val physicalProperties =
+            setOf(DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED)
+        val updatedState =
+            DeviceState(
+                DeviceState.Configuration.Builder(
+                        DEVICE_STATE_HALF_FOLDED.identifier,
+                        DEVICE_STATE_HALF_FOLDED.name
+                    )
+                    .setPhysicalProperties(physicalProperties)
+                    .build()
+            )
+        // state change with updated physical properties shouldn't cause a posture change
+        deviceStateCallback.value.onDeviceStateChanged(updatedState)
         assertThat(posture).isEqualTo(DEVICE_POSTURE_HALF_OPENED)
 
-        // WHEN the display state maps to using the base state, then posture updates
-        deviceStateCallback.value.onStateChanged(useBaseStateDeviceState)
+        // WHEN the display state maps to the physical state, then posture updates
+        deviceStateCallback.value.onDeviceStateChanged(DEVICE_STATE_USE_BASE_STATE)
         assertThat(posture).isEqualTo(DEVICE_POSTURE_CLOSED)
     }
 
@@ -124,20 +149,97 @@
         var numPostureChanges = 0
         underTest.addCallback { numPostureChanges++ }
 
-        deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_HALF_OPENED)
+        deviceStateCallback.value.onDeviceStateChanged(DEVICE_STATE_HALF_FOLDED)
         assertThat(numPostureChanges).isEqualTo(1)
 
-        // base state changes doesn't send another posture update since the device state isn't
-        // useBaseStateDeviceState
-        deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_CLOSED)
-        deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_HALF_OPENED)
-        deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_FLIPPED)
-        deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_OPENED)
-        deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_UNKNOWN)
+        // update to physical properties doesn't send another posture update since the device state
+        // isn't useBaseStateDeviceState
+        deviceStateCallback.value.onDeviceStateChanged(
+            getStateUpdatedPhysicalProperties(DEVICE_STATE_HALF_FOLDED, DEVICE_STATE_CLOSED)
+        )
+        deviceStateCallback.value.onDeviceStateChanged(
+            getStateUpdatedPhysicalProperties(DEVICE_STATE_HALF_FOLDED, DEVICE_STATE_HALF_FOLDED)
+        )
+        deviceStateCallback.value.onDeviceStateChanged(
+            getStateUpdatedPhysicalProperties(DEVICE_STATE_HALF_FOLDED, DEVICE_STATE_OPENED)
+        )
+        deviceStateCallback.value.onDeviceStateChanged(
+            getStateUpdatedPhysicalProperties(DEVICE_STATE_HALF_FOLDED, DEVICE_STATE_UNKNOWN)
+        )
         assertThat(numPostureChanges).isEqualTo(1)
     }
 
     private fun verifyRegistersForDeviceStateCallback() {
         verify(deviceStateManager).registerCallback(eq(mainExecutor), deviceStateCallback.capture())
     }
+
+    private fun getStateUpdatedPhysicalProperties(
+        currentState: DeviceState,
+        physicalState: DeviceState
+    ): DeviceState {
+        return DeviceState(
+            DeviceState.Configuration.Builder(currentState.identifier, currentState.name)
+                .setSystemProperties(currentState.configuration.systemProperties)
+                .setPhysicalProperties(physicalState.configuration.physicalProperties)
+                .build()
+        )
+    }
+
+    companion object {
+        val DEVICE_STATE_CLOSED =
+            DeviceState(
+                DeviceState.Configuration.Builder(
+                        DEVICE_POSTURE_CLOSED /* id */,
+                        "CLOSED" /* name */
+                    )
+                    .setPhysicalProperties(
+                        setOf(DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED)
+                    )
+                    .build()
+            )
+        val DEVICE_STATE_HALF_FOLDED =
+            DeviceState(
+                DeviceState.Configuration.Builder(
+                        DEVICE_POSTURE_HALF_OPENED /* id */,
+                        "HALF_FOLDED" /* name */
+                    )
+                    .setPhysicalProperties(
+                        setOf(
+                            DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN
+                        )
+                    )
+                    .build()
+            )
+        val DEVICE_STATE_OPENED =
+            DeviceState(
+                DeviceState.Configuration.Builder(
+                        DEVICE_POSTURE_OPENED /* id */,
+                        "OPENED" /* name */
+                    )
+                    .setPhysicalProperties(
+                        setOf(DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN)
+                    )
+                    .build()
+            )
+        val DEVICE_STATE_FLIPPED =
+            DeviceState(
+                DeviceState.Configuration.Builder(
+                        DEVICE_POSTURE_FLIPPED /* id */,
+                        "FLIPPED" /* name */
+                    )
+                    .build()
+            )
+        val DEVICE_STATE_UNKNOWN =
+            DeviceState(
+                DeviceState.Configuration.Builder(
+                        DEVICE_POSTURE_UNKNOWN /* id */,
+                        "UNKNOWN" /* name */
+                    )
+                    .build()
+            )
+        val DEVICE_STATE_USE_BASE_STATE =
+            DeviceState(
+                DeviceState.Configuration.Builder(SUPPORTED_POSTURES_SIZE, "USE_BASE_STATE").build()
+            )
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
index 4ccbd1b..2955162 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
@@ -25,6 +25,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.verify;
 
+import android.hardware.devicestate.DeviceState;
 import android.hardware.devicestate.DeviceStateManager;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -119,11 +120,11 @@
                 0, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
         mFakeRotationPolicy.setRotationLock(true);
 
-        mDeviceStateCallback.onStateChanged(1);
+        mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(1));
         assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
 
         // Settings only exist for state 0 and 1
-        mDeviceStateCallback.onStateChanged(2);
+        mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(2));
 
         assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
     }
@@ -134,10 +135,10 @@
                 0, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, 1, DEVICE_STATE_ROTATION_LOCK_LOCKED);
         mFakeRotationPolicy.setRotationLock(true);
 
-        mDeviceStateCallback.onStateChanged(0);
+        mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(0));
         assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
 
-        mDeviceStateCallback.onStateChanged(1);
+        mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(1));
         assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue();
     }
 
@@ -147,7 +148,7 @@
         mFakeRotationPolicy.setRotationLock(true);
 
         // State 2 -> Ignored -> Fall back to state 1 which is unlocked
-        mDeviceStateCallback.onStateChanged(2);
+        mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(2));
 
         assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
     }
@@ -161,7 +162,7 @@
         mFakeRotationPolicy.setRotationLock(false);
 
         // State 2 -> Ignored -> Fall back to state 1 which is locked
-        mDeviceStateCallback.onStateChanged(2);
+        mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(2));
 
         assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue();
     }
@@ -173,7 +174,7 @@
         mSettingsManager.onPersistedSettingsChanged();
         mFakeRotationPolicy.setRotationLock(true);
 
-        mDeviceStateCallback.onStateChanged(0);
+        mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(0));
         assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue();
 
         mDeviceStateRotationLockSettingController.onRotationLockStateChanged(
@@ -189,10 +190,10 @@
 
     @Test
     public void whenDeviceStateSwitchedToIgnoredState_useFallbackSetting() {
-        mDeviceStateCallback.onStateChanged(0);
+        mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(0));
         assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue();
 
-        mDeviceStateCallback.onStateChanged(2);
+        mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(2));
         assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
     }
 
@@ -202,10 +203,10 @@
                 8, DEVICE_STATE_ROTATION_LOCK_IGNORED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
         mFakeRotationPolicy.setRotationLock(true);
 
-        mDeviceStateCallback.onStateChanged(1);
+        mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(1));
         assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
 
-        mDeviceStateCallback.onStateChanged(8);
+        mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(8));
         assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
 
         mDeviceStateRotationLockSettingController.onRotationLockStateChanged(
@@ -225,7 +226,7 @@
                 0, DEVICE_STATE_ROTATION_LOCK_UNLOCKED,
                 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
         mFakeRotationPolicy.setRotationLock(false);
-        mDeviceStateCallback.onStateChanged(0);
+        mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(0));
 
         assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
 
@@ -241,7 +242,7 @@
         initializeSettingsWith(
                 0, DEVICE_STATE_ROTATION_LOCK_LOCKED,
                 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
-        mDeviceStateCallback.onStateChanged(0);
+        mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(0));
 
         mDeviceStateRotationLockSettingController.onRotationLockStateChanged(
                 /* rotationLocked= */ false,
@@ -262,7 +263,7 @@
                 0, DEVICE_STATE_ROTATION_LOCK_LOCKED,
                 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED,
                 2, DEVICE_STATE_ROTATION_LOCK_IGNORED);
-        mDeviceStateCallback.onStateChanged(2);
+        mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(2));
 
         mDeviceStateRotationLockSettingController.onRotationLockStateChanged(
                 /* rotationLocked= */ true,
@@ -283,8 +284,8 @@
                 0, DEVICE_STATE_ROTATION_LOCK_LOCKED,
                 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED,
                 8, DEVICE_STATE_ROTATION_LOCK_IGNORED);
-        mDeviceStateCallback.onStateChanged(1);
-        mDeviceStateCallback.onStateChanged(8);
+        mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(1));
+        mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(8));
 
         mDeviceStateRotationLockSettingController.onRotationLockStateChanged(
                 /* rotationLocked= */ true,
@@ -320,6 +321,10 @@
         mSettingsManager.onPersistedSettingsChanged();
     }
 
+    private DeviceState createDeviceStateForIdentifier(int id) {
+        return new DeviceState(new DeviceState.Configuration.Builder(id, "" /* name */).build());
+    }
+
     private static class FakeRotationPolicy implements RotationPolicyWrapper {
 
         private boolean mRotationLock;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index 76913e8..e4b9f10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.fromGoneTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.kosmos.testScope
@@ -63,9 +64,9 @@
             ConfigurationInteractor(FakeConfigurationRepository()),
             FakeShadeRepository(),
             kosmos.keyguardTransitionInteractor,
-        ) {
-            kosmos.sceneInteractor
-        }
+            { kosmos.sceneInteractor },
+            { kosmos.fromGoneTransitionInteractor },
+        )
     private val keyguardStatusBarInteractor =
         KeyguardStatusBarInteractor(
             FakeKeyguardStatusBarRepository(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 19f31d5..ec27f48 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -441,7 +441,8 @@
                 new ConfigurationInteractor(configurationRepository),
                 shadeRepository,
                 keyguardTransitionInteractor,
-                () -> sceneInteractor);
+                () -> sceneInteractor,
+                () -> mKosmos.getFromGoneTransitionInteractor());
 
         mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
         mFromPrimaryBouncerTransitionInteractor =
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/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/jank/InteractionJankMonitorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/jank/InteractionJankMonitorKosmos.kt
index 5c5016d..e2b5869 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/jank/InteractionJankMonitorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/jank/InteractionJankMonitorKosmos.kt
@@ -16,9 +16,24 @@
 
 package com.android.systemui.jank
 
+import android.os.HandlerThread
 import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.jank.InteractionJankMonitor.Configuration.Builder
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.spy
 
-val Kosmos.interactionJankMonitor by Fixture<InteractionJankMonitor> { mock() }
+val Kosmos.interactionJankMonitor by
+    Fixture<InteractionJankMonitor> {
+        spy(InteractionJankMonitor(HandlerThread("InteractionJankMonitor-Kosmos"))).apply {
+            doReturn(true).`when`(this).shouldMonitor()
+            doReturn(true).`when`(this).begin(any(), anyInt())
+            doReturn(true).`when`(this).begin(any<Builder>())
+            doReturn(true).`when`(this).end(anyInt())
+            doReturn(true).`when`(this).cancel(anyInt())
+            doReturn(true).`when`(this).cancel(anyInt(), anyInt())
+        }
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
index 3893a9b7..00cdc33 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
@@ -51,6 +51,7 @@
         configurationRepository: FakeConfigurationRepository = FakeConfigurationRepository(),
         shadeRepository: FakeShadeRepository = FakeShadeRepository(),
         sceneInteractor: SceneInteractor = mock(),
+        fromGoneTransitionInteractor: FromGoneTransitionInteractor = mock(),
         powerInteractor: PowerInteractor = PowerInteractorFactory.create().powerInteractor,
     ): WithDependencies {
         // Mock this until the class is replaced by kosmos
@@ -77,6 +78,7 @@
                 sceneInteractorProvider = { sceneInteractor },
                 keyguardTransitionInteractor = keyguardTransitionInteractor,
                 powerInteractor = powerInteractor,
+                fromGoneTransitionInteractor = { fromGoneTransitionInteractor },
             ),
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
index 5140a9f..d61bc9f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
@@ -26,7 +26,7 @@
 import com.android.systemui.shade.data.repository.shadeRepository
 import com.android.systemui.statusbar.commandQueue
 
-val Kosmos.keyguardInteractor by
+val Kosmos.keyguardInteractor: KeyguardInteractor by
     Kosmos.Fixture {
         KeyguardInteractor(
             repository = keyguardRepository,
@@ -38,5 +38,6 @@
             shadeRepository = shadeRepository,
             keyguardTransitionInteractor = keyguardTransitionInteractor,
             sceneInteractorProvider = { sceneInteractor },
+            fromGoneTransitionInteractor = { fromGoneTransitionInteractor },
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
index 6cc1e8e..185deda 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
@@ -20,11 +20,13 @@
 import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
 
 val Kosmos.keyguardTransitionInteractor: KeyguardTransitionInteractor by
     Kosmos.Fixture {
         KeyguardTransitionInteractor(
             scope = applicationCoroutineScope,
+            mainDispatcher = testDispatcher,
             repository = keyguardTransitionRepository,
             keyguardRepository = keyguardRepository,
             fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor },
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/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/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 3fc5af1..e861892 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.jank.interactionJankMonitor
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.fromGoneTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.fromLockscreenTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.fromPrimaryBouncerTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
@@ -92,6 +93,7 @@
     val fromPrimaryBouncerTransitionInteractor by lazy {
         kosmos.fromPrimaryBouncerTransitionInteractor
     }
+    val fromGoneTransitionInteractor by lazy { kosmos.fromGoneTransitionInteractor }
     val globalActionsInteractor by lazy { kosmos.globalActionsInteractor }
     val sceneDataSource by lazy { kosmos.sceneDataSource }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java b/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/scroll/FakeScrollCaptureConnection.java
similarity index 97%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/scroll/FakeScrollCaptureConnection.java
index 63f7c97..ea59c0a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/scroll/FakeScrollCaptureConnection.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 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,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
 
 import android.content.pm.ActivityInfo;
 import android.graphics.Canvas;
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeSession.java b/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/scroll/FakeSession.java
similarity index 97%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeSession.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/scroll/FakeSession.java
index 478658e..3b7b158 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeSession.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/scroll/FakeSession.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 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,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
 
 import static android.util.MathUtils.constrain;
 
@@ -32,6 +32,8 @@
 import android.media.Image;
 import android.util.Log;
 
+import com.android.systemui.screenshot.scroll.ScrollCaptureClient;
+
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 
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/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
index 7a86c4f..c6684af 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
@@ -30,9 +30,9 @@
 import com.android.systemui.plugins.activityStarter
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.settings.userTracker
+import com.android.systemui.shade.domain.interactor.panelExpansionInteractor
 import com.android.systemui.shade.domain.interactor.shadeAnimationInteractor
 import com.android.systemui.shade.shadeController
-import com.android.systemui.shade.shadeViewController
 import com.android.systemui.statusbar.commandQueue
 import com.android.systemui.statusbar.notification.collection.provider.launchFullScreenIntentProvider
 import com.android.systemui.statusbar.notification.collection.render.notificationVisibilityProvider
@@ -78,7 +78,7 @@
             statusBarNotificationActivityStarterLogger,
             onUserInteractionCallback,
             notificationPresenter,
-            shadeViewController,
+            panelExpansionInteractor,
             notificationShadeWindowController,
             activityTransitionAnimator,
             shadeAnimationInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index 32d572e..2d5a361 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -63,6 +63,8 @@
 
     override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false)
 
+    override val satelliteConnectionHysteresisSeconds = MutableStateFlow(0)
+
     private var isInEcmMode: Boolean = false
 
     override suspend fun isInEcmMode(): Boolean = isInEcmMode
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeDeviceProvisionedController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeDeviceProvisionedController.kt
index aa2c2a2..ebcabf8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeDeviceProvisionedController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeDeviceProvisionedController.kt
@@ -39,8 +39,16 @@
         callbacks.toSet().forEach { it.onUserSwitched() }
     }
 
-    fun setUserSetup(userId: Int) {
-        usersSetup.add(userId)
+    fun setUserSetup(userId: Int, isSetup: Boolean = true) {
+        if (isSetup) {
+            usersSetup.add(userId)
+        } else {
+            usersSetup.remove(userId)
+        }
         callbacks.toSet().forEach { it.onUserSetupChanged() }
     }
+
+    fun setCurrentUserSetup(isSetup: Boolean) {
+        setUserSetup(currentUser, isSetup)
+    }
 }
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index 3239175..004f37c 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -528,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() {
@@ -713,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();
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/ravenwood/api-maintainers.md b/ravenwood/api-maintainers.md
index 4b2f968..c059cab 100644
--- a/ravenwood/api-maintainers.md
+++ b/ravenwood/api-maintainers.md
@@ -4,7 +4,7 @@
 
 To opt-in to supporting an API under Ravenwood, you can use the inline annotations documented below to customize your API behavior when running under Ravenwood.  Because these annotations are inline in the relevant platform source code, they serve as valuable reminders to future API maintainers of Ravenwood support expectations.
 
-> **Note:** to ensure that API teams are well-supported during early Ravenwood onboarding, the Ravenwood team is manually maintaining an allow-list of classes that are able to use Ravenwood annotations.  Please reach out to ravenwood@ so we can offer design advice and allow-list your APIs.
+> **Note:** Active development of Ravenwood has been paused as of March 2024. Currently supported APIs will continue working, but the addition of new APIs is not currently being supported. There is an allowlist that restricts where Ravenwood-specific annotations can be used, and that allowlist is not being expanded while development is paused.
 
 These Ravenwood-specific annotations have no bearing on the status of an API being public, `@SystemApi`, `@TestApi`, `@hide`, etc.  Ravenwood annotations are an orthogonal concept that are only consumed by the internal `hoststubgen` tool during a post-processing step that generates the Ravenwood runtime environment.  Teams that own APIs can continue to refactor opted-in `@hide` implementation details, as long as the test-visible behavior continues passing.
 
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 4be303a..f3e53fe 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) {
@@ -5123,18 +5225,16 @@
         private int mSystemUiUid = 0;
 
         AccessibilityDisplayListener(Context context, Handler handler) {
-            if (Flags.addWindowTokenWithoutLock()) {
-                // Avoid concerns about one thread adding displays while another thread removes
-                // them by ensuring the looper is the main looper and the DisplayListener
-                // callbacks are always executed on the one main thread.
-                final boolean isMainHandler = handler.getLooper() == Looper.getMainLooper();
-                final String errorMessage =
-                        "AccessibilityDisplayListener must use the main handler";
-                if (Build.IS_USERDEBUG || Build.IS_ENG) {
-                    Preconditions.checkArgument(isMainHandler, errorMessage);
-                } else if (!isMainHandler) {
-                    Slog.e(LOG_TAG, errorMessage);
-                }
+            // Avoid concerns about one thread adding displays while another thread removes
+            // them by ensuring the looper is the main looper and the DisplayListener
+            // callbacks are always executed on the one main thread.
+            final boolean isMainHandler = handler.getLooper() == Looper.getMainLooper();
+            final String errorMessage =
+                    "AccessibilityDisplayListener must use the main handler";
+            if (Build.IS_USERDEBUG || Build.IS_ENG) {
+                Preconditions.checkArgument(isMainHandler, errorMessage);
+            } else if (!isMainHandler) {
+                Slog.e(LOG_TAG, errorMessage);
             }
 
             mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
@@ -5178,14 +5278,12 @@
 
         @Override
         public void onDisplayAdded(int displayId) {
-            if (Flags.addWindowTokenWithoutLock()) {
-                final boolean isMainThread = Looper.getMainLooper().isCurrentThread();
-                final String errorMessage = "onDisplayAdded must be called from the main thread";
-                if (Build.IS_USERDEBUG || Build.IS_ENG) {
-                    Preconditions.checkArgument(isMainThread, errorMessage);
-                } else if (!isMainThread) {
-                    Slog.e(LOG_TAG, errorMessage);
-                }
+            final boolean isMainThread = Looper.getMainLooper().isCurrentThread();
+            final String errorMessage = "onDisplayAdded must be called from the main thread";
+            if (Build.IS_USERDEBUG || Build.IS_ENG) {
+                Preconditions.checkArgument(isMainThread, errorMessage);
+            } else if (!isMainThread) {
+                Slog.e(LOG_TAG, errorMessage);
             }
             final Display display = mDisplayManager.getDisplay(displayId);
             if (!isValidDisplay(display)) {
@@ -5201,41 +5299,27 @@
                     mInputFilter.onDisplayAdded(display);
                 }
                 AccessibilityUserState userState = getCurrentUserStateLocked();
-                if (Flags.addWindowTokenWithoutLock()) {
-                    services = new ArrayList<>(userState.mBoundServices);
-                } else {
-                    services = userState.mBoundServices;
-                    if (displayId != Display.DEFAULT_DISPLAY) {
-                        for (int i = 0; i < services.size(); i++) {
-                            AccessibilityServiceConnection boundClient = services.get(i);
-                            boundClient.addWindowTokenForDisplay(displayId);
-                        }
-                    }
-                }
+                services = new ArrayList<>(userState.mBoundServices);
                 updateMagnificationLocked(userState);
                 updateWindowsForAccessibilityCallbackLocked(userState);
                 notifyClearAccessibilityCacheLocked();
             }
-            if (Flags.addWindowTokenWithoutLock()) {
-                if (displayId != Display.DEFAULT_DISPLAY) {
-                    for (int i = 0; i < services.size(); i++) {
-                        AccessibilityServiceConnection boundClient = services.get(i);
-                        boundClient.addWindowTokenForDisplay(displayId);
-                    }
+            if (displayId != Display.DEFAULT_DISPLAY) {
+                for (int i = 0; i < services.size(); i++) {
+                    AccessibilityServiceConnection boundClient = services.get(i);
+                    boundClient.addWindowTokenForDisplay(displayId);
                 }
             }
         }
 
         @Override
         public void onDisplayRemoved(int displayId) {
-            if (Flags.addWindowTokenWithoutLock()) {
-                final boolean isMainThread = Looper.getMainLooper().isCurrentThread();
-                final String errorMessage = "onDisplayRemoved must be called from the main thread";
-                if (Build.IS_USERDEBUG || Build.IS_ENG) {
-                    Preconditions.checkArgument(isMainThread, errorMessage);
-                } else if (!isMainThread) {
-                    Slog.e(LOG_TAG, errorMessage);
-                }
+            final boolean isMainThread = Looper.getMainLooper().isCurrentThread();
+            final String errorMessage = "onDisplayRemoved must be called from the main thread";
+            if (Build.IS_USERDEBUG || Build.IS_ENG) {
+                Preconditions.checkArgument(isMainThread, errorMessage);
+            } else if (!isMainThread) {
+                Slog.e(LOG_TAG, errorMessage);
             }
             synchronized (mLock) {
                 if (!removeDisplayFromList(displayId)) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index b90a66a..fb28055 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -220,7 +220,7 @@
     @Override
     public void onServiceConnected(ComponentName componentName, IBinder service) {
         AccessibilityUserState userState = mUserStateWeakReference.get();
-        if (userState != null && Flags.addWindowTokenWithoutLock()) {
+        if (userState != null) {
             addWindowTokensForAllDisplays();
         }
         synchronized (mLock) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 063eafe..9a1d379 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;
@@ -232,9 +243,6 @@
 
     void addServiceLocked(AccessibilityServiceConnection serviceConnection) {
         if (!mBoundServices.contains(serviceConnection)) {
-            if (!Flags.addWindowTokenWithoutLock()) {
-                serviceConnection.addWindowTokensForAllDisplays();
-            }
             mBoundServices.add(serviceConnection);
             mComponentNameToServiceMap.put(serviceConnection.getComponentName(), serviceConnection);
             mServiceInfoChangeListener.onServiceInfoChangedLocked(this);
@@ -566,7 +574,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 +1110,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/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index f69104d..aad9e24 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -136,9 +136,6 @@
             return;
         }
 
-        if (!Flags.addWindowTokenWithoutLock()) {
-            mUiAutomationService.addWindowTokensForAllDisplays();
-        }
         // UiAutomationService#connectServiceUnknownThread posts to a handler
         // so this call should return immediately.
         mUiAutomationService.connectServiceUnknownThread();
@@ -286,9 +283,7 @@
                     // If the serviceInterface is null, the UiAutomation has been shut down on
                     // another thread.
                     if (serviceInterface != null) {
-                        if (Flags.addWindowTokenWithoutLock()) {
-                            mUiAutomationService.addWindowTokensForAllDisplays();
-                        }
+                        mUiAutomationService.addWindowTokensForAllDisplays();
                         if (mTrace.isA11yTracingEnabledForTypes(
                                 AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) {
                             mTrace.logTrace("UiAutomationService.connectServiceUnknownThread",
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/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index 3645c40..9c84b12 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -545,6 +545,25 @@
         });
     }
 
+    /**
+     * Set views_fillable_total_count as long as mEventInternal presents.
+     */
+    public void maybeSetViewFillableCounts(int totalFillableCount) {
+        mEventInternal.ifPresent(event -> {
+            event.mViewFillableTotalCount = totalFillableCount;
+        });
+    }
+
+    /**
+     * Set views_filled_failure_count using failure count as long as mEventInternal
+     * presents.
+     */
+    public void maybeSetViewFillFailureCounts(int failureCount) {
+        mEventInternal.ifPresent(event -> {
+            event.mViewFillFailureCount = failureCount;
+        });
+    }
+
     public void logAndEndEvent() {
         if (!mEventInternal.isPresent()) {
             Slog.w(TAG, "Shouldn't be logging AutofillPresentationEventReported again for same "
@@ -587,7 +606,9 @@
                     + " mFieldClassificationRequestId=" + event.mFieldClassificationRequestId
                     + " mAppPackageUid=" + mCallingAppUid
                     + " mIsCredentialRequest=" + event.mIsCredentialRequest
-                    + " mWebviewRequestedCredential=" + event.mWebviewRequestedCredential);
+                    + " mWebviewRequestedCredential=" + event.mWebviewRequestedCredential
+                    + " mViewFillableTotalCount=" + event.mViewFillableTotalCount
+                    + " mViewFillFailureCount=" + event.mViewFillFailureCount);
         }
 
         // TODO(b/234185326): Distinguish empty responses from other no presentation reasons.
@@ -628,7 +649,9 @@
                 event.mFieldClassificationRequestId,
                 mCallingAppUid,
                 event.mIsCredentialRequest,
-                event.mWebviewRequestedCredential);
+                event.mWebviewRequestedCredential,
+                event.mViewFillableTotalCount,
+                event.mViewFillFailureCount);
         mEventInternal = Optional.empty();
     }
 
@@ -664,6 +687,8 @@
         int mFieldClassificationRequestId = -1;
         boolean mIsCredentialRequest = false;
         boolean mWebviewRequestedCredential = false;
+        int mViewFillableTotalCount = -1;
+        int mViewFillFailureCount = -1;
 
         PresentationStatsEventInternal() {}
     }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index ca2a3dd..d006cf6 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -170,6 +170,7 @@
 import android.util.SparseArray;
 import android.util.TimeUtils;
 import android.view.KeyEvent;
+import android.view.autofill.AutofillFeatureFlags;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
 import android.view.autofill.AutofillManager.AutofillCommitReason;
@@ -596,6 +597,17 @@
 
     private boolean mIgnoreViewStateResetToEmpty;
 
+    /*
+     * Id of the previous view that was entered. Once set, it would only be replaced by non-null
+     * view ids.
+     * When a user focuses on a field, autofill request is sent. When the keyboard pops up, or the
+     * autofill dialog shows up, this field loses focus. After selecting a suggestion, focus goes
+     * back to the same field. This field allows to ignore focus loss when autofill dialog comes up.
+     * TODO(b/319872477): Note that there maybe some cases where we incorrectly detect focus loss.
+     */
+    @GuardedBy("mLock")
+    private @Nullable AutofillId mPreviousNonNullEnteredViewId;
+
     void onSwitchInputMethodLocked() {
         // One caveat is that for the case where the focus is on a field for which regular autofill
         // returns null, and augmented autofill is triggered,  and then the user switches the input
@@ -1498,7 +1510,7 @@
         mSessionCommittedEventLogger.maybeSetComponentPackageUid(uid);
         mSaveEventLogger = SaveEventLogger.forSessionId(sessionId);
         mIsPrimaryCredential = isPrimaryCredential;
-        mIgnoreViewStateResetToEmpty = Flags.ignoreViewStateResetToEmpty();
+        mIgnoreViewStateResetToEmpty = AutofillFeatureFlags.shouldIgnoreViewStateResetToEmpty();
 
         synchronized (mLock) {
             mSessionFlags = new SessionFlags();
@@ -4389,6 +4401,7 @@
             case ACTION_START_SESSION:
                 // View is triggering autofill.
                 mCurrentViewId = viewState.id;
+                mPreviousNonNullEnteredViewId = viewState.id;
                 viewState.update(value, virtualBounds, flags);
                 startNewEventForPresentationStatsEventLogger();
                 mPresentationStatsEventLogger.maybeSetIsNewRequest(true);
@@ -4458,6 +4471,14 @@
                 if (value != null) {
                     viewState.setCurrentValue(value);
                 }
+                // isSameViewEntered has some limitations, where it isn't considered same view when
+                // autofill suggestions pop up, user selects, and the focus lands back on the view.
+                // isSameViewAgain tries to overcome that situation.
+                final boolean isSameViewAgain = isSameViewEntered
+                        || Objects.equals(mCurrentViewId, mPreviousNonNullEnteredViewId);
+                if (mCurrentViewId != null) {
+                    mPreviousNonNullEnteredViewId = mCurrentViewId;
+                }
                 boolean isCredmanRequested = (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0;
                 if (shouldRequestSecondaryProvider(flags)) {
                     if (requestNewFillResponseOnViewEnteredIfNecessaryLocked(
@@ -4509,7 +4530,8 @@
                 // With Fill Dialog, request starts prior to view getting entered. So, we can't end
                 // the event at this moment, otherwise we will be wrongly attributing fill dialog
                 // event as concluded.
-                if (!wasPreviouslyFillDialog) {
+                if (!wasPreviouslyFillDialog && !isSameViewAgain) {
+                    // TODO(b/319872477): Re-consider this logic below
                     mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
                             NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED);
                     mPresentationStatsEventLogger.logAndEndEvent();
@@ -4587,10 +4609,10 @@
                         mCurrentViewId = null;
                     }
 
-
+                    // It's not necessary that there's no more presentation for this view. It could
+                    // be that the user chose some suggestion, in which case, view exits.
                     mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
                                 NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED);
-                    mPresentationStatsEventLogger.logAndEndEvent();
                 }
                 break;
             default:
@@ -5326,6 +5348,9 @@
      */
     @GuardedBy("mLock")
     void setAutofillFailureLocked(@NonNull List<AutofillId> ids) {
+        if (sVerbose && !ids.isEmpty()) {
+            Slog.v(TAG, "Total views that failed to populate: " + ids.size());
+        }
         for (int i = 0; i < ids.size(); i++) {
             final AutofillId id = ids.get(i);
             final ViewState viewState = mViewStates.get(id);
@@ -5340,6 +5365,8 @@
                 Slog.v(TAG, "Changed state of " + id + " to " + viewState.getStateAsString());
             }
         }
+        mPresentationStatsEventLogger.maybeSetViewFillFailureCounts(ids.size());
+        mPresentationStatsEventLogger.logAndEndEvent();
     }
 
     @GuardedBy("mLock")
@@ -6525,8 +6552,11 @@
                     if (waitingDatasetAuth) {
                         mUi.hideFillUi(this);
                     }
+                    if (sVerbose) {
+                        Slog.v(TAG, "Total views to be autofilled: " + ids.size());
+                    }
+                    mPresentationStatsEventLogger.maybeSetViewFillableCounts(ids.size());
                     if (sDebug) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
-
                     mClient.autofill(id, ids, values, hideHighlight);
                     if (dataset.getId() != null) {
                         if (mSelectedDatasetIds == null) {
diff --git a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
index f2409fb..5e52e06 100644
--- a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
+++ b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
@@ -18,7 +18,8 @@
 
 import static android.os.UserHandle.getCallingUserId;
 
-import static com.android.server.companion.CompanionDeviceManagerService.PerUserAssociationSet;
+import static com.android.server.companion.association.AssociationDiskStore.readAssociationsFromPayload;
+import static com.android.server.companion.utils.RolesUtils.addRoleHolderForAssociation;
 
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
@@ -26,62 +27,50 @@
 import android.companion.AssociationInfo;
 import android.companion.Flags;
 import android.companion.datatransfer.SystemDataTransferRequest;
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManagerInternal;
-import android.util.ArraySet;
-import android.util.Log;
 import android.util.Slog;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.CollectionUtils;
 import com.android.server.companion.association.AssociationDiskStore;
 import com.android.server.companion.association.AssociationRequestsProcessor;
 import com.android.server.companion.association.AssociationStore;
+import com.android.server.companion.association.Associations;
 import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
 
 import java.nio.ByteBuffer;
-import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
-import java.util.Set;
 import java.util.function.Predicate;
 
 @SuppressLint("LongLogTag")
 class BackupRestoreProcessor {
-    static final String TAG = "CDM_BackupRestoreProcessor";
+    private static final String TAG = "CDM_BackupRestoreProcessor";
     private static final int BACKUP_AND_RESTORE_VERSION = 0;
 
+    private final Context mContext;
     @NonNull
-    private final CompanionDeviceManagerService mService;
-    @NonNull
-    private final PackageManagerInternal mPackageManager;
+    private final PackageManagerInternal mPackageManagerInternal;
     @NonNull
     private final AssociationStore mAssociationStore;
     @NonNull
-    private final AssociationDiskStore mPersistentStore;
+    private final AssociationDiskStore mAssociationDiskStore;
     @NonNull
     private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
     @NonNull
     private final AssociationRequestsProcessor mAssociationRequestsProcessor;
 
-    /**
-     * A structure that consists of a set of restored associations that are pending corresponding
-     * companion app to be installed.
-     */
-    @GuardedBy("mAssociationsPendingAppInstall")
-    private final PerUserAssociationSet mAssociationsPendingAppInstall =
-            new PerUserAssociationSet();
-
-    BackupRestoreProcessor(@NonNull CompanionDeviceManagerService service,
+    BackupRestoreProcessor(@NonNull Context context,
+                           @NonNull PackageManagerInternal packageManagerInternal,
                            @NonNull AssociationStore associationStore,
-                           @NonNull AssociationDiskStore persistentStore,
+                           @NonNull AssociationDiskStore associationDiskStore,
                            @NonNull SystemDataTransferRequestStore systemDataTransferRequestStore,
                            @NonNull AssociationRequestsProcessor associationRequestsProcessor) {
-        mService = service;
-        mPackageManager = service.mPackageManagerInternal;
+        mContext = context;
+        mPackageManagerInternal = packageManagerInternal;
         mAssociationStore = associationStore;
-        mPersistentStore = persistentStore;
+        mAssociationDiskStore = associationDiskStore;
         mSystemDataTransferRequestStore = systemDataTransferRequestStore;
         mAssociationRequestsProcessor = associationRequestsProcessor;
     }
@@ -93,9 +82,9 @@
      * | (4) SystemDataTransferRequest length | SystemDataTransferRequest XML (without userId)|
      */
     byte[] getBackupPayload(int userId) {
-        // Persist state first to generate an up-to-date XML file
-        mService.persistStateForUser(userId);
-        byte[] associationsPayload = mPersistentStore.getBackupPayload(userId);
+        Slog.i(TAG, "getBackupPayload() userId=[" + userId + "].");
+
+        byte[] associationsPayload = mAssociationDiskStore.getBackupPayload(userId);
         int associationsPayloadLength = associationsPayload.length;
 
         // System data transfer requests are persisted up-to-date already
@@ -119,6 +108,9 @@
      * Create new associations and system data transfer request consents using backed up payload.
      */
     void applyRestoredPayload(byte[] payload, int userId) {
+        Slog.i(TAG, "applyRestoredPayload() userId=[" + userId + "], payload size=["
+                + payload.length + "].");
+
         ByteBuffer buffer = ByteBuffer.wrap(payload);
 
         // Make sure that payload version matches current version to ensure proper deserialization
@@ -131,9 +123,8 @@
         // Read the bytes containing backed-up associations
         byte[] associationsPayload = new byte[buffer.getInt()];
         buffer.get(associationsPayload);
-        final Set<AssociationInfo> restoredAssociations = new HashSet<>();
-        mPersistentStore.readStateFromPayload(associationsPayload, userId,
-                restoredAssociations, new HashMap<>());
+        final Associations restoredAssociations = readAssociationsFromPayload(
+                associationsPayload, userId);
 
         // Read the bytes containing backed-up system data transfer requests user consent
         byte[] requestsPayload = new byte[buffer.getInt()];
@@ -142,13 +133,13 @@
                 mSystemDataTransferRequestStore.readRequestsFromPayload(requestsPayload, userId);
 
         // Get a list of installed packages ahead of time.
-        List<ApplicationInfo> installedApps = mPackageManager.getInstalledApplications(
+        List<ApplicationInfo> installedApps = mPackageManagerInternal.getInstalledApplications(
                 0, userId, getCallingUserId());
 
         // Restored device may have a different user ID than the backed-up user's user-ID. Since
         // association ID is dependent on the user ID, restored associations must account for
         // this potential difference on their association IDs.
-        for (AssociationInfo restored : restoredAssociations) {
+        for (AssociationInfo restored : restoredAssociations.getAssociations()) {
             // Don't restore a revoked association. Since they weren't added to the device being
             // restored in the first place, there is no need to worry about revoking a role that
             // was never granted either.
@@ -168,10 +159,9 @@
 
             // Create a new association reassigned to this user and a valid association ID
             final String packageName = restored.getPackageName();
-            final int newId = mService.getNewAssociationIdForPackage(userId, packageName);
-            AssociationInfo newAssociation =
-                    new AssociationInfo.Builder(newId, userId, packageName, restored)
-                            .build();
+            final int newId = mAssociationStore.getNextId(userId);
+            AssociationInfo newAssociation = new AssociationInfo.Builder(newId, userId, packageName,
+                    restored).build();
 
             // Check if the companion app for this association is already installed, then do one
             // of the following:
@@ -179,13 +169,15 @@
             // the role attached to this association to the app.
             // (2) If the app isn't yet installed, then add this association to the list of pending
             // associations to be added when the package is installed in the future.
-            boolean isPackageInstalled = installedApps.stream()
-                    .anyMatch(app -> packageName.equals(app.packageName));
+            boolean isPackageInstalled = installedApps.stream().anyMatch(
+                    app -> packageName.equals(app.packageName));
             if (isPackageInstalled) {
                 mAssociationRequestsProcessor.maybeGrantRoleAndStoreAssociation(newAssociation,
                         null, null);
             } else {
-                addToPendingAppInstall(newAssociation);
+                newAssociation = (new AssociationInfo.Builder(newAssociation)).setPending(true)
+                        .build();
+                mAssociationStore.addAssociation(newAssociation);
             }
 
             // Re-map restored system data transfer requests to newly created associations
@@ -195,32 +187,27 @@
                 mSystemDataTransferRequestStore.writeRequest(userId, newRequest);
             }
         }
-
-        // Persist restored state.
-        mService.persistStateForUser(userId);
     }
 
-    void addToPendingAppInstall(@NonNull AssociationInfo association) {
-        association = (new AssociationInfo.Builder(association))
-                .setPending(true)
-                .build();
-
-        synchronized (mAssociationsPendingAppInstall) {
-            mAssociationsPendingAppInstall.forUser(association.getUserId()).add(association);
+    public void restorePendingAssociations(int userId, String packageName) {
+        List<AssociationInfo> pendingAssociations = mAssociationStore.getPendingAssociations(userId,
+                packageName);
+        if (!pendingAssociations.isEmpty()) {
+            Slog.i(TAG, "Found pending associations for package=[" + packageName
+                    + "]. Restoring...");
         }
-    }
-
-    void removeFromPendingAppInstall(@NonNull AssociationInfo association) {
-        synchronized (mAssociationsPendingAppInstall) {
-            mAssociationsPendingAppInstall.forUser(association.getUserId()).remove(association);
-        }
-    }
-
-    @NonNull
-    Set<AssociationInfo> getAssociationsPendingAppInstallForUser(@UserIdInt int userId) {
-        synchronized (mAssociationsPendingAppInstall) {
-            // Return a copy.
-            return new ArraySet<>(mAssociationsPendingAppInstall.forUser(userId));
+        for (AssociationInfo association : pendingAssociations) {
+            AssociationInfo newAssociation = new AssociationInfo.Builder(association)
+                    .setPending(false)
+                    .build();
+            addRoleHolderForAssociation(mContext, newAssociation, success -> {
+                if (success) {
+                    mAssociationStore.updateAssociation(newAssociation);
+                    Slog.i(TAG, "Association=[" + association + "] is restored.");
+                } else {
+                    Slog.e(TAG, "Failed to restore association=[" + association + "].");
+                }
+            });
         }
     }
 
@@ -231,7 +218,7 @@
     private boolean handleCollision(@UserIdInt int userId,
             AssociationInfo restored,
             List<SystemDataTransferRequest> restoredRequests) {
-        List<AssociationInfo> localAssociations = mAssociationStore.getAssociationsForPackage(
+        List<AssociationInfo> localAssociations = mAssociationStore.getActiveAssociationsByPackage(
                 restored.getUserId(), restored.getPackageName());
         Predicate<AssociationInfo> isSameDevice = associationInfo -> {
             boolean matchesMacAddress = Objects.equals(
@@ -248,7 +235,7 @@
             return false;
         }
 
-        Log.d(TAG, "Conflict detected with association id=" + local.getId()
+        Slog.d(TAG, "Conflict detected with association id=" + local.getId()
                 + " while restoring CDM backup. Keeping local association.");
 
         List<SystemDataTransferRequest> localRequests = mSystemDataTransferRequestStore
@@ -266,8 +253,8 @@
                 continue;
             }
 
-            Log.d(TAG, "Restoring " + restoredRequest.getClass().getSimpleName()
-                    + " to an existing association id=" + local.getId() + ".");
+            Slog.d(TAG, "Restoring " + restoredRequest.getClass().getSimpleName()
+                    + " to an existing association id=[" + local.getId() + "].");
 
             SystemDataTransferRequest newRequest =
                     restoredRequest.copyWithNewId(local.getId());
diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
index c801489..0a41485 100644
--- a/services/companion/java/com/android/server/companion/CompanionApplicationController.java
+++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
@@ -397,7 +397,7 @@
         // First, disable hint mode for Auto profile and mark not BOUND for primary service ONLY.
         if (isPrimary) {
             final List<AssociationInfo> associations =
-                    mAssociationStore.getAssociationsForPackage(userId, packageName);
+                    mAssociationStore.getActiveAssociationsByPackage(userId, packageName);
 
             for (AssociationInfo association : associations) {
                 final String deviceProfile = association.getDeviceProfile();
@@ -442,7 +442,7 @@
                 mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
 
         for (AssociationInfo ai :
-                mAssociationStore.getAssociationsForPackage(userId, packageName)) {
+                mAssociationStore.getActiveAssociationsByPackage(userId, packageName)) {
             final int associationId = ai.getId();
             stillAssociated = true;
             if (ai.isSelfManaged()) {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 3846e98..73ebbc7 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -37,12 +37,9 @@
 import static com.android.internal.util.CollectionUtils.any;
 import static com.android.internal.util.Preconditions.checkState;
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-import static com.android.server.companion.association.AssociationStore.CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED;
-import static com.android.server.companion.utils.AssociationUtils.getFirstAssociationIdForUser;
-import static com.android.server.companion.utils.AssociationUtils.getLastAssociationIdForUser;
-import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed;
 import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature;
 import static com.android.server.companion.utils.PackageUtils.getPackageInfo;
+import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed;
 import static com.android.server.companion.utils.PermissionsUtils.checkCallerCanManageCompanionDevice;
 import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
 import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanObservingDevicePresenceByUuid;
@@ -82,20 +79,16 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.UserInfo;
 import android.hardware.power.Mode;
 import android.net.MacAddress;
 import android.net.NetworkPolicyManager;
 import android.os.Binder;
 import android.os.Environment;
-import android.os.Handler;
-import android.os.Message;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.ParcelUuid;
+import android.os.PowerExemptionManager;
 import android.os.PowerManagerInternal;
-import android.os.PowerWhitelistManager;
-import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
@@ -105,13 +98,9 @@
 import android.util.ExceptionUtils;
 import android.util.Log;
 import android.util.Slog;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.content.PackageMonitor;
-import com.android.internal.infra.PerUser;
 import com.android.internal.notification.NotificationAccessConfirmationActivityContract;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.ArrayUtils;
@@ -121,8 +110,8 @@
 import com.android.server.SystemService;
 import com.android.server.companion.association.AssociationDiskStore;
 import com.android.server.companion.association.AssociationRequestsProcessor;
-import com.android.server.companion.association.AssociationRevokeProcessor;
 import com.android.server.companion.association.AssociationStore;
+import com.android.server.companion.association.DisassociationProcessor;
 import com.android.server.companion.association.InactiveAssociationsRemovalService;
 import com.android.server.companion.datatransfer.SystemDataTransferProcessor;
 import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
@@ -139,7 +128,6 @@
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -164,80 +152,51 @@
     private static final long ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT = DAYS.toMillis(90);
     private static final int MAX_CN_LENGTH = 500;
 
-    private final ActivityManager mActivityManager;
-    private AssociationDiskStore mAssociationDiskStore;
-    private final PersistUserStateHandler mUserPersistenceHandler;
-
-    private final AssociationStore mAssociationStore;
-    private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
-    private AssociationRequestsProcessor mAssociationRequestsProcessor;
-    private SystemDataTransferProcessor mSystemDataTransferProcessor;
-    private BackupRestoreProcessor mBackupRestoreProcessor;
-    private CompanionDevicePresenceMonitor mDevicePresenceMonitor;
-    private CompanionApplicationController mCompanionAppController;
-    private CompanionTransportManager mTransportManager;
-    private AssociationRevokeProcessor mAssociationRevokeProcessor;
-
     private final ActivityTaskManagerInternal mAtmInternal;
     private final ActivityManagerInternal mAmInternal;
     private final IAppOpsService mAppOpsManager;
-    private final PowerWhitelistManager mPowerWhitelistManager;
-    private final UserManager mUserManager;
-    public final PackageManagerInternal mPackageManagerInternal;
+    private final PowerExemptionManager mPowerExemptionManager;
+    private final PackageManagerInternal mPackageManagerInternal;
     private final PowerManagerInternal mPowerManagerInternal;
 
-    /**
-     * A structure that consists of two nested maps, and effectively maps (userId + packageName) to
-     * a list of IDs that have been previously assigned to associations for that package.
-     * We maintain this structure so that we never re-use association IDs for the same package
-     * (until it's uninstalled).
-     */
-    @GuardedBy("mPreviouslyUsedIds")
-    private final SparseArray<Map<String, Set<Integer>>> mPreviouslyUsedIds = new SparseArray<>();
-
-    private final RemoteCallbackList<IOnAssociationsChangedListener> mListeners =
-            new RemoteCallbackList<>();
-
-    private CrossDeviceSyncController mCrossDeviceSyncController;
-
-    private ObservableUuidStore mObservableUuidStore;
+    private final AssociationStore mAssociationStore;
+    private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
+    private final ObservableUuidStore mObservableUuidStore;
+    private final AssociationRequestsProcessor mAssociationRequestsProcessor;
+    private final SystemDataTransferProcessor mSystemDataTransferProcessor;
+    private final BackupRestoreProcessor mBackupRestoreProcessor;
+    private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
+    private final CompanionApplicationController mCompanionAppController;
+    private final CompanionTransportManager mTransportManager;
+    private final DisassociationProcessor mDisassociationProcessor;
+    private final CrossDeviceSyncController mCrossDeviceSyncController;
 
     public CompanionDeviceManagerService(Context context) {
         super(context);
 
-        mActivityManager = context.getSystemService(ActivityManager.class);
-        mPowerWhitelistManager = context.getSystemService(PowerWhitelistManager.class);
+        final ActivityManager activityManager = context.getSystemService(ActivityManager.class);
+        mPowerExemptionManager = context.getSystemService(PowerExemptionManager.class);
         mAppOpsManager = IAppOpsService.Stub.asInterface(
                 ServiceManager.getService(Context.APP_OPS_SERVICE));
         mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
         mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
-        mUserManager = context.getSystemService(UserManager.class);
-
-        mUserPersistenceHandler = new PersistUserStateHandler();
-        mAssociationStore = new AssociationStore();
-        mSystemDataTransferRequestStore = new SystemDataTransferRequestStore();
-
+        final UserManager userManager = context.getSystemService(UserManager.class);
         mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
+
+        final AssociationDiskStore associationDiskStore = new AssociationDiskStore();
+        mAssociationStore = new AssociationStore(userManager, associationDiskStore);
+        mSystemDataTransferRequestStore = new SystemDataTransferRequestStore();
         mObservableUuidStore = new ObservableUuidStore();
-    }
 
-    @Override
-    public void onStart() {
-        final Context context = getContext();
+        // Init processors
+        mAssociationRequestsProcessor = new AssociationRequestsProcessor(context,
+                mPackageManagerInternal, mAssociationStore);
+        mBackupRestoreProcessor = new BackupRestoreProcessor(context, mPackageManagerInternal,
+                mAssociationStore, associationDiskStore, mSystemDataTransferRequestStore,
+                mAssociationRequestsProcessor);
 
-        mAssociationDiskStore = new AssociationDiskStore();
-        mAssociationRequestsProcessor = new AssociationRequestsProcessor(
-                /* cdmService */ this, mAssociationStore);
-        mBackupRestoreProcessor = new BackupRestoreProcessor(
-                /* cdmService */ this, mAssociationStore, mAssociationDiskStore,
-                mSystemDataTransferRequestStore, mAssociationRequestsProcessor);
-
-        mObservableUuidStore.getObservableUuidsForUser(getContext().getUserId());
-
-        mAssociationStore.registerListener(mAssociationStoreChangeListener);
-
-        mDevicePresenceMonitor = new CompanionDevicePresenceMonitor(mUserManager,
+        mDevicePresenceMonitor = new CompanionDevicePresenceMonitor(userManager,
                 mAssociationStore, mObservableUuidStore, mDevicePresenceCallback);
 
         mCompanionAppController = new CompanionApplicationController(
@@ -246,11 +205,9 @@
 
         mTransportManager = new CompanionTransportManager(context, mAssociationStore);
 
-        mAssociationRevokeProcessor = new AssociationRevokeProcessor(this, mAssociationStore,
-                mPackageManagerInternal, mDevicePresenceMonitor, mCompanionAppController,
-                mSystemDataTransferRequestStore, mTransportManager);
-
-        loadAssociationsFromDisk();
+        mDisassociationProcessor = new DisassociationProcessor(context, activityManager,
+                mAssociationStore, mPackageManagerInternal, mDevicePresenceMonitor,
+                mCompanionAppController, mSystemDataTransferRequestStore, mTransportManager);
 
         mSystemDataTransferProcessor = new SystemDataTransferProcessor(this,
                 mPackageManagerInternal, mAssociationStore,
@@ -258,6 +215,16 @@
 
         // TODO(b/279663946): move context sync to a dedicated system service
         mCrossDeviceSyncController = new CrossDeviceSyncController(getContext(), mTransportManager);
+    }
+
+    @Override
+    public void onStart() {
+        // Init association stores
+        mAssociationStore.refreshCache();
+        mAssociationStore.registerLocalListener(mAssociationStoreChangeListener);
+
+        // Init UUID store
+        mObservableUuidStore.getObservableUuidsForUser(getContext().getUserId());
 
         // Publish "binder" service.
         final CompanionDeviceManagerImpl impl = new CompanionDeviceManagerImpl();
@@ -267,50 +234,6 @@
         LocalServices.addService(CompanionDeviceManagerServiceInternal.class, new LocalService());
     }
 
-    void loadAssociationsFromDisk() {
-        final Set<AssociationInfo> allAssociations = new ArraySet<>();
-        synchronized (mPreviouslyUsedIds) {
-            List<Integer> userIds = new ArrayList<>();
-            for (UserInfo user : mUserManager.getAliveUsers()) {
-                userIds.add(user.id);
-            }
-            // The data is stored in DE directories, so we can read the data for all users now
-            // (which would not be possible if the data was stored to CE directories).
-            mAssociationDiskStore.readStateForUsers(userIds, allAssociations, mPreviouslyUsedIds);
-        }
-
-        final Set<AssociationInfo> activeAssociations =
-                new ArraySet<>(/* capacity */ allAssociations.size());
-        // A set contains the userIds that need to persist state after remove the app
-        // from the list of role holders.
-        final Set<Integer> usersToPersistStateFor = new ArraySet<>();
-
-        for (AssociationInfo association : allAssociations) {
-            if (association.isPending()) {
-                mBackupRestoreProcessor.addToPendingAppInstall(association);
-            } else if (!association.isRevoked()) {
-                activeAssociations.add(association);
-            } else if (mAssociationRevokeProcessor.maybeRemoveRoleHolderForAssociation(
-                    association)) {
-                // Nothing more to do here, but we'll need to persist all the associations to the
-                // disk afterwards.
-                usersToPersistStateFor.add(association.getUserId());
-            } else {
-                mAssociationRevokeProcessor.addToPendingRoleHolderRemoval(association);
-            }
-        }
-
-        mAssociationStore.setAssociationsToCache(activeAssociations);
-
-        // IMPORTANT: only do this AFTER mAssociationStore.setAssociations(), because
-        // persistStateForUser() queries AssociationStore.
-        // (If persistStateForUser() is invoked before mAssociationStore.setAssociations() it
-        // would effectively just clear-out all the persisted associations).
-        for (int userId : usersToPersistStateFor) {
-            persistStateForUser(userId);
-        }
-    }
-
     @Override
     public void onBootPhase(int phase) {
         final Context context = getContext();
@@ -329,8 +252,10 @@
 
     @Override
     public void onUserUnlocking(@NonNull TargetUser user) {
+        Slog.d(TAG, "onUserUnlocking...");
         final int userId = user.getUserIdentifier();
-        final List<AssociationInfo> associations = mAssociationStore.getAssociationsForUser(userId);
+        final List<AssociationInfo> associations = mAssociationStore.getActiveAssociationsByUser(
+                userId);
 
         if (associations.isEmpty()) return;
 
@@ -359,7 +284,8 @@
                         ? Collections.emptyList() : Arrays.asList(bluetoothDeviceUuids);
 
                 for (AssociationInfo ai :
-                        mAssociationStore.getAssociationsByAddress(bluetoothDevice.getAddress())) {
+                        mAssociationStore.getActiveAssociationsByAddress(
+                                bluetoothDevice.getAddress())) {
                     Slog.i(TAG, "onUserUnlocked, device id( " + ai.getId() + " ) is connected");
                     mDevicePresenceMonitor.onBluetoothCompanionDeviceConnected(ai.getId());
                 }
@@ -379,7 +305,7 @@
     @NonNull
     AssociationInfo getAssociationWithCallerChecks(
             @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) {
-        AssociationInfo association = mAssociationStore.getAssociationsForPackageWithAddress(
+        AssociationInfo association = mAssociationStore.getFirstAssociationByAddress(
                 userId, packageName, macAddress);
         association = sanitizeWithCallerChecks(getContext(), association);
         if (association != null) {
@@ -533,7 +459,7 @@
      */
     private boolean shouldBindPackage(@UserIdInt int userId, @NonNull String packageName) {
         final List<AssociationInfo> packageAssociations =
-                mAssociationStore.getAssociationsForPackage(userId, packageName);
+                mAssociationStore.getActiveAssociationsByPackage(userId, packageName);
         final List<ObservableUuid> observableUuids =
                 mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
 
@@ -551,77 +477,6 @@
         return false;
     }
 
-    private void onAssociationChangedInternal(
-            @AssociationStore.ChangeType int changeType, AssociationInfo association) {
-        final int id = association.getId();
-        final int userId = association.getUserId();
-        final String packageName = association.getPackageName();
-
-        if (changeType == AssociationStore.CHANGE_TYPE_REMOVED) {
-            markIdAsPreviouslyUsedForPackage(id, userId, packageName);
-        }
-
-        final List<AssociationInfo> updatedAssociations =
-                mAssociationStore.getAssociationsForUser(userId);
-
-        mUserPersistenceHandler.postPersistUserState(userId);
-
-        // Notify listeners if ADDED, REMOVED or UPDATED_ADDRESS_CHANGED.
-        // Do NOT notify when UPDATED_ADDRESS_UNCHANGED, which means a minor tweak in association's
-        // configs, which "listeners" won't (and shouldn't) be able to see.
-        if (changeType != CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED) {
-            notifyListeners(userId, updatedAssociations);
-        }
-        updateAtm(userId, updatedAssociations);
-    }
-
-    void persistStateForUser(@UserIdInt int userId) {
-        // We want to store both active associations and the revoked (removed) association that we
-        // are keeping around for the final clean-up (delayed role holder removal).
-        final List<AssociationInfo> allAssociations;
-        // Start with the active associations - these we can get from the AssociationStore.
-        allAssociations = new ArrayList<>(
-                mAssociationStore.getAssociationsForUser(userId));
-        // ... and add the revoked (removed) association, that are yet to be permanently removed.
-        allAssociations.addAll(
-                mAssociationRevokeProcessor.getPendingRoleHolderRemovalAssociationsForUser(userId));
-        // ... and add the restored associations that are pending missing package installation.
-        allAssociations.addAll(mBackupRestoreProcessor
-                .getAssociationsPendingAppInstallForUser(userId));
-
-        final Map<String, Set<Integer>> usedIdsForUser = getPreviouslyUsedIdsForUser(userId);
-
-        mAssociationDiskStore.persistStateForUser(userId, allAssociations, usedIdsForUser);
-    }
-
-    private void notifyListeners(
-            @UserIdInt int userId, @NonNull List<AssociationInfo> associations) {
-        mListeners.broadcast((listener, callbackUserId) -> {
-            int listenerUserId = (int) callbackUserId;
-            if (listenerUserId == userId || listenerUserId == UserHandle.USER_ALL) {
-                try {
-                    listener.onAssociationsChanged(associations);
-                } catch (RemoteException ignored) {
-                }
-            }
-        });
-    }
-
-    private void markIdAsPreviouslyUsedForPackage(
-            int associationId, @UserIdInt int userId, @NonNull String packageName) {
-        synchronized (mPreviouslyUsedIds) {
-            Map<String, Set<Integer>> usedIdsForUser = mPreviouslyUsedIds.get(userId);
-            if (usedIdsForUser == null) {
-                usedIdsForUser = new HashMap<>();
-                mPreviouslyUsedIds.put(userId, usedIdsForUser);
-            }
-
-            final Set<Integer> usedIdsForPackage =
-                    usedIdsForUser.computeIfAbsent(packageName, it -> new HashSet<>());
-            usedIdsForPackage.add(associationId);
-        }
-    }
-
     private void onPackageRemoveOrDataClearedInternal(
             @UserIdInt int userId, @NonNull String packageName) {
         if (DEBUG) {
@@ -629,19 +484,20 @@
                     + packageName);
         }
 
-        // Clear associations.
+        // Clear all associations for the package.
         final List<AssociationInfo> associationsForPackage =
-                mAssociationStore.getAssociationsForPackage(userId, packageName);
+                mAssociationStore.getAssociationsByPackage(userId, packageName);
+        if (!associationsForPackage.isEmpty()) {
+            Slog.i(TAG, "Package removed or data cleared for user=[" + userId + "], package=["
+                    + packageName + "]. Cleaning up CDM data...");
+        }
+        for (AssociationInfo association : associationsForPackage) {
+            mDisassociationProcessor.disassociate(association.getId());
+        }
+
+        // Clear observable UUIDs for the package.
         final List<ObservableUuid> uuidsTobeObserved =
                 mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
-        for (AssociationInfo association : associationsForPackage) {
-            mAssociationStore.removeAssociation(association.getId());
-        }
-        // Clear role holders
-        for (AssociationInfo association : associationsForPackage) {
-            mAssociationRevokeProcessor.maybeRemoveRoleHolderForAssociation(association);
-        }
-        // Clear the uuids to be observed.
         for (ObservableUuid uuid : uuidsTobeObserved) {
             mObservableUuidStore.removeObservableUuid(userId, uuid.getUuid(), packageName);
         }
@@ -652,31 +508,13 @@
     private void onPackageModifiedInternal(@UserIdInt int userId, @NonNull String packageName) {
         if (DEBUG) Log.i(TAG, "onPackageModified() u" + userId + "/" + packageName);
 
-        final List<AssociationInfo> associationsForPackage =
-                mAssociationStore.getAssociationsForPackage(userId, packageName);
-        for (AssociationInfo association : associationsForPackage) {
-            updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(),
-                    association.getPackageName());
-        }
+        updateSpecialAccessPermissionForAssociatedPackage(userId, packageName);
 
         mCompanionAppController.onPackagesChanged(userId);
     }
 
     private void onPackageAddedInternal(@UserIdInt int userId, @NonNull String packageName) {
-        if (DEBUG) Log.i(TAG, "onPackageAddedInternal() u" + userId + "/" + packageName);
-
-        Set<AssociationInfo> associationsPendingAppInstall = mBackupRestoreProcessor
-                .getAssociationsPendingAppInstallForUser(userId);
-        for (AssociationInfo association : associationsPendingAppInstall) {
-            if (!packageName.equals(association.getPackageName())) continue;
-
-            AssociationInfo newAssociation = new AssociationInfo.Builder(association)
-                    .setPending(false)
-                    .build();
-            mAssociationRequestsProcessor.maybeGrantRoleAndStoreAssociation(newAssociation,
-                    null, null);
-            mBackupRestoreProcessor.removeFromPendingAppInstall(association);
-        }
+        mBackupRestoreProcessor.restorePendingAssociations(userId, packageName);
     }
 
     // Revoke associations if the selfManaged companion device does not connect for 3 months.
@@ -698,7 +536,7 @@
             final int id = association.getId();
 
             Slog.i(TAG, "Removing inactive self-managed association id=" + id);
-            mAssociationRevokeProcessor.disassociateInternal(id);
+            mDisassociationProcessor.disassociate(id);
         }
     }
 
@@ -750,7 +588,7 @@
                 enforceUsesCompanionDeviceFeature(getContext(), userId, packageName);
             }
 
-            return mAssociationStore.getAssociationsForPackage(userId, packageName);
+            return mAssociationStore.getActiveAssociationsByPackage(userId, packageName);
         }
 
         @Override
@@ -761,9 +599,9 @@
             enforceCallerIsSystemOrCanInteractWithUserId(getContext(), userId);
 
             if (userId == UserHandle.USER_ALL) {
-                return List.copyOf(mAssociationStore.getAssociations());
+                return mAssociationStore.getActiveAssociations();
             }
-            return mAssociationStore.getAssociationsForUser(userId);
+            return mAssociationStore.getActiveAssociationsByUser(userId);
         }
 
         @Override
@@ -773,7 +611,8 @@
             addOnAssociationsChangedListener_enforcePermission();
 
             enforceCallerIsSystemOrCanInteractWithUserId(getContext(), userId);
-            mListeners.register(listener, userId);
+
+            mAssociationStore.registerRemoteListener(listener, userId);
         }
 
         @Override
@@ -784,7 +623,7 @@
 
             enforceCallerIsSystemOrCanInteractWithUserId(getContext(), userId);
 
-            mListeners.unregister(listener);
+            mAssociationStore.unregisterRemoteListener(listener);
         }
 
         @Override
@@ -843,16 +682,16 @@
 
             final AssociationInfo association =
                     getAssociationWithCallerChecks(userId, packageName, deviceMacAddress);
-            mAssociationRevokeProcessor.disassociateInternal(association.getId());
+            mDisassociationProcessor.disassociate(association.getId());
         }
 
         @Override
         public void disassociate(int associationId) {
-            Log.i(TAG, "disassociate() associationId=" + associationId);
+            Slog.i(TAG, "disassociate() associationId=" + associationId);
 
             final AssociationInfo association =
                     getAssociationWithCallerChecks(associationId);
-            mAssociationRevokeProcessor.disassociateInternal(association.getId());
+            mDisassociationProcessor.disassociate(association.getId());
         }
 
         @Override
@@ -867,8 +706,7 @@
                 throw new IllegalArgumentException("Component name is too long.");
             }
 
-            final long identity = Binder.clearCallingIdentity();
-            try {
+            return Binder.withCleanCallingIdentity(() -> {
                 if (!isRestrictedSettingsAllowed(getContext(), callingPackage, callingUid)) {
                     Slog.e(TAG, "Side loaded app must enable restricted "
                             + "setting before request the notification access");
@@ -882,9 +720,7 @@
                                 | PendingIntent.FLAG_CANCEL_CURRENT,
                         null /* options */,
                         new UserHandle(userId));
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
+            });
         }
 
         /**
@@ -912,7 +748,7 @@
                 return true;
             }
 
-            return any(mAssociationStore.getAssociationsForPackage(userId, packageName),
+            return any(mAssociationStore.getActiveAssociationsByPackage(userId, packageName),
                     a -> a.isLinkedTo(macAddress));
         }
 
@@ -1166,7 +1002,7 @@
             final int userId = getCallingUserId();
             enforceCallerIsSystemOr(userId, packageName);
 
-            AssociationInfo association = mAssociationStore.getAssociationsForPackageWithAddress(
+            AssociationInfo association = mAssociationStore.getFirstAssociationByAddress(
                     userId, packageName, deviceAddress);
 
             if (association == null) {
@@ -1239,14 +1075,15 @@
 
             enforceUsesCompanionDeviceFeature(getContext(), userId, callingPackage);
             checkState(!ArrayUtils.isEmpty(
-                            mAssociationStore.getAssociationsForPackage(userId, callingPackage)),
+                            mAssociationStore.getActiveAssociationsByPackage(userId,
+                                    callingPackage)),
                     "App must have an association before calling this API");
         }
 
         @Override
         public boolean canPairWithoutPrompt(String packageName, String macAddress, int userId) {
             final AssociationInfo association =
-                    mAssociationStore.getAssociationsForPackageWithAddress(
+                    mAssociationStore.getFirstAssociationByAddress(
                             userId, packageName, macAddress);
             if (association == null) {
                 return false;
@@ -1269,13 +1106,11 @@
 
         @Override
         public byte[] getBackupPayload(int userId) {
-            Log.i(TAG, "getBackupPayload() userId=" + userId);
             return mBackupRestoreProcessor.getBackupPayload(userId);
         }
 
         @Override
         public void applyRestoredPayload(byte[] payload, int userId) {
-            Log.i(TAG, "applyRestoredPayload() userId=" + userId);
             mBackupRestoreProcessor.applyRestoredPayload(payload, userId);
         }
 
@@ -1286,7 +1121,7 @@
             return new CompanionDeviceShellCommand(CompanionDeviceManagerService.this,
                     mAssociationStore, mDevicePresenceMonitor, mTransportManager,
                     mSystemDataTransferProcessor, mAssociationRequestsProcessor,
-                    mBackupRestoreProcessor, mAssociationRevokeProcessor)
+                    mBackupRestoreProcessor, mDisassociationProcessor)
                     .exec(this, in.getFileDescriptor(), out.getFileDescriptor(),
                             err.getFileDescriptor(), args);
         }
@@ -1314,88 +1149,6 @@
                 /* callback */ null, /* resultReceiver */ null);
     }
 
-    @NonNull
-    private Map<String, Set<Integer>> getPreviouslyUsedIdsForUser(@UserIdInt int userId) {
-        synchronized (mPreviouslyUsedIds) {
-            return getPreviouslyUsedIdsForUserLocked(userId);
-        }
-    }
-
-    @GuardedBy("mPreviouslyUsedIds")
-    @NonNull
-    private Map<String, Set<Integer>> getPreviouslyUsedIdsForUserLocked(@UserIdInt int userId) {
-        final Map<String, Set<Integer>> usedIdsForUser = mPreviouslyUsedIds.get(userId);
-        if (usedIdsForUser == null) {
-            return Collections.emptyMap();
-        }
-        return deepUnmodifiableCopy(usedIdsForUser);
-    }
-
-    @GuardedBy("mPreviouslyUsedIds")
-    @NonNull
-    private Set<Integer> getPreviouslyUsedIdsForPackageLocked(
-            @UserIdInt int userId, @NonNull String packageName) {
-        // "Deeply unmodifiable" map: the map itself and the Set<Integer> values it contains are all
-        // unmodifiable.
-        final Map<String, Set<Integer>> usedIdsForUser = getPreviouslyUsedIdsForUserLocked(userId);
-        final Set<Integer> usedIdsForPackage = usedIdsForUser.get(packageName);
-
-        if (usedIdsForPackage == null) {
-            return Collections.emptySet();
-        }
-
-        //The set is already unmodifiable.
-        return usedIdsForPackage;
-    }
-
-    /**
-     * Get a new association id for the package.
-     */
-    public int getNewAssociationIdForPackage(@UserIdInt int userId, @NonNull String packageName) {
-        synchronized (mPreviouslyUsedIds) {
-            // First: collect all IDs currently in use for this user's Associations.
-            final SparseBooleanArray usedIds = new SparseBooleanArray();
-
-            // We should really only be checking associations for the given user (i.e.:
-            // mAssociationStore.getAssociationsForUser(userId)), BUT in the past we've got in a
-            // state where association IDs were not assigned correctly in regard to
-            // user-to-association-ids-range (e.g. associations with IDs from 1 to 100,000 should
-            // always belong to u0), so let's check all the associations.
-            for (AssociationInfo it : mAssociationStore.getAssociations()) {
-                usedIds.put(it.getId(), true);
-            }
-
-            // Some IDs may be reserved by associations that aren't stored yet due to missing
-            // package after a backup restoration. We don't want the ID to have been taken by
-            // another association by the time when it is activated from the package installation.
-            final Set<AssociationInfo> pendingAssociations = mBackupRestoreProcessor
-                    .getAssociationsPendingAppInstallForUser(userId);
-            for (AssociationInfo it : pendingAssociations) {
-                usedIds.put(it.getId(), true);
-            }
-
-            // Second: collect all IDs that have been previously used for this package (and user).
-            final Set<Integer> previouslyUsedIds =
-                    getPreviouslyUsedIdsForPackageLocked(userId, packageName);
-
-            int id = getFirstAssociationIdForUser(userId);
-            final int lastAvailableIdForUser = getLastAssociationIdForUser(userId);
-
-            // Find first ID that isn't used now AND has never been used for the given package.
-            while (usedIds.get(id) || previouslyUsedIds.contains(id)) {
-                // Increment and try again
-                id++;
-                // ... but first check if the ID is valid (within the range allocated to the user).
-                if (id > lastAvailableIdForUser) {
-                    throw new RuntimeException("Cannot create a new Association ID for "
-                            + packageName + " for user " + userId);
-                }
-            }
-
-            return id;
-        }
-    }
-
     /**
      * Update special access for the association's package
      */
@@ -1403,20 +1156,27 @@
         final PackageInfo packageInfo =
                 getPackageInfo(getContext(), userId, packageName);
 
-        Binder.withCleanCallingIdentity(() -> updateSpecialAccessPermissionAsSystem(packageInfo));
+        Binder.withCleanCallingIdentity(() -> updateSpecialAccessPermissionAsSystem(packageInfo,
+                userId, packageName));
     }
 
-    private void updateSpecialAccessPermissionAsSystem(PackageInfo packageInfo) {
+    private void updateSpecialAccessPermissionAsSystem(PackageInfo packageInfo, int userId,
+            String packageName) {
         if (packageInfo == null) {
             return;
         }
+
+        List<AssociationInfo> associations = mAssociationStore.getActiveAssociationsByPackage(
+                userId, packageName);
+
         if (containsEither(packageInfo.requestedPermissions,
                 android.Manifest.permission.RUN_IN_BACKGROUND,
-                android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)) {
-            mPowerWhitelistManager.addToWhitelist(packageInfo.packageName);
+                android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)
+                && !associations.isEmpty()) {
+            mPowerExemptionManager.addToPermanentAllowList(packageInfo.packageName);
         } else {
             try {
-                mPowerWhitelistManager.removeFromWhitelist(packageInfo.packageName);
+                mPowerExemptionManager.removeFromPermanentAllowList(packageInfo.packageName);
             } catch (UnsupportedOperationException e) {
                 Slog.w(TAG, packageInfo.packageName + " can't be removed from power save"
                         + " whitelist. It might due to the package is whitelisted by the system.");
@@ -1427,7 +1187,8 @@
         try {
             if (containsEither(packageInfo.requestedPermissions,
                     android.Manifest.permission.USE_DATA_IN_BACKGROUND,
-                    android.Manifest.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND)) {
+                    android.Manifest.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND)
+                    && !associations.isEmpty()) {
                 networkPolicyManager.addUidPolicy(
                         packageInfo.applicationInfo.uid,
                         NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
@@ -1487,7 +1248,7 @@
 
             try {
                 final List<AssociationInfo> associations =
-                        mAssociationStore.getAssociationsForUser(userId);
+                        mAssociationStore.getActiveAssociationsByUser(userId);
                 for (AssociationInfo a : associations) {
                     try {
                         int uid = pm.getPackageUidAsUser(a.getPackageName(), userId);
@@ -1506,7 +1267,16 @@
             new AssociationStore.OnChangeListener() {
                 @Override
                 public void onAssociationChanged(int changeType, AssociationInfo association) {
-                    onAssociationChangedInternal(changeType, association);
+                    Slog.d(TAG, "onAssociationChanged changeType=[" + changeType
+                            + "], association=[" + association);
+
+                    final int userId = association.getUserId();
+                    final List<AssociationInfo> updatedAssociations =
+                            mAssociationStore.getActiveAssociationsByUser(userId);
+
+                    updateAtm(userId, updatedAssociations);
+                    updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(),
+                            association.getPackageName());
                 }
             };
 
@@ -1634,64 +1404,4 @@
             }
         }
     }
-
-    /**
-     * This method must only be called from {@link CompanionDeviceShellCommand} for testing
-     * purposes only!
-     */
-    void persistState() {
-        mUserPersistenceHandler.clearMessages();
-        for (UserInfo user : mUserManager.getAliveUsers()) {
-            persistStateForUser(user.id);
-        }
-    }
-
-    /**
-     * This class is dedicated to handling requests to persist user state.
-     */
-    @SuppressLint("HandlerLeak")
-    private class PersistUserStateHandler extends Handler {
-        PersistUserStateHandler() {
-            super(BackgroundThread.get().getLooper());
-        }
-
-        /**
-         * Persists user state unless there is already an outstanding request for the given user.
-         */
-        synchronized void postPersistUserState(@UserIdInt int userId) {
-            if (!hasMessages(userId)) {
-                sendMessage(obtainMessage(userId));
-            }
-        }
-
-        /**
-         * Clears *ALL* outstanding persist requests for *ALL* users.
-         */
-        synchronized void clearMessages() {
-            removeCallbacksAndMessages(null);
-        }
-
-        @Override
-        public void handleMessage(@NonNull Message msg) {
-            final int userId = msg.what;
-            persistStateForUser(userId);
-        }
-    }
-
-    /**
-     * Persist associations
-     */
-    public void postPersistUserState(@UserIdInt int userId) {
-        mUserPersistenceHandler.postPersistUserState(userId);
-    }
-
-    /**
-     * Set to store associations
-     */
-    public static class PerUserAssociationSet extends PerUser<Set<AssociationInfo>> {
-        @Override
-        protected @NonNull Set<AssociationInfo> create(int userId) {
-            return new ArraySet<>();
-        }
-    }
 }
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 16877dc..a7a73cb 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -33,8 +33,8 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.server.companion.association.AssociationRequestsProcessor;
-import com.android.server.companion.association.AssociationRevokeProcessor;
 import com.android.server.companion.association.AssociationStore;
+import com.android.server.companion.association.DisassociationProcessor;
 import com.android.server.companion.datatransfer.SystemDataTransferProcessor;
 import com.android.server.companion.datatransfer.contextsync.BitmapUtils;
 import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController;
@@ -49,7 +49,7 @@
     private static final String TAG = "CDM_CompanionDeviceShellCommand";
 
     private final CompanionDeviceManagerService mService;
-    private final AssociationRevokeProcessor mRevokeProcessor;
+    private final DisassociationProcessor mDisassociationProcessor;
     private final AssociationStore mAssociationStore;
     private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
     private final CompanionTransportManager mTransportManager;
@@ -65,7 +65,7 @@
             SystemDataTransferProcessor systemDataTransferProcessor,
             AssociationRequestsProcessor associationRequestsProcessor,
             BackupRestoreProcessor backupRestoreProcessor,
-            AssociationRevokeProcessor revokeProcessor) {
+            DisassociationProcessor disassociationProcessor) {
         mService = service;
         mAssociationStore = associationStore;
         mDevicePresenceMonitor = devicePresenceMonitor;
@@ -73,7 +73,7 @@
         mSystemDataTransferProcessor = systemDataTransferProcessor;
         mAssociationRequestsProcessor = associationRequestsProcessor;
         mBackupRestoreProcessor = backupRestoreProcessor;
-        mRevokeProcessor = revokeProcessor;
+        mDisassociationProcessor = disassociationProcessor;
     }
 
     @Override
@@ -105,12 +105,15 @@
                 case "list": {
                     final int userId = getNextIntArgRequired();
                     final List<AssociationInfo> associationsForUser =
-                            mAssociationStore.getAssociationsForUser(userId);
+                            mAssociationStore.getActiveAssociationsByUser(userId);
+                    final int maxId = mAssociationStore.getMaxId(userId);
+                    out.println("Max ID: " + maxId);
+                    out.println("Association ID | Package Name | Mac Address");
                     for (AssociationInfo association : associationsForUser) {
                         // TODO(b/212535524): use AssociationInfo.toShortString(), once it's not
                         //  longer referenced in tests.
-                        out.println(association.getPackageName() + " "
-                                + association.getDeviceMacAddress() + " " + association.getId());
+                        out.println(association.getId() + " | " + association.getPackageName()
+                                + " | " + association.getDeviceMacAddress());
                     }
                 }
                 break;
@@ -132,28 +135,24 @@
                     final String address = getNextArgRequired();
                     final AssociationInfo association =
                             mService.getAssociationWithCallerChecks(userId, packageName, address);
-                    if (association != null) {
-                        mRevokeProcessor.disassociateInternal(association.getId());
-                    }
+                    mDisassociationProcessor.disassociate(association.getId());
                 }
                 break;
 
                 case "disassociate-all": {
                     final int userId = getNextIntArgRequired();
-                    final String packageName = getNextArgRequired();
                     final List<AssociationInfo> userAssociations =
-                            mAssociationStore.getAssociationsForPackage(userId, packageName);
+                            mAssociationStore.getAssociationsByUser(userId);
                     for (AssociationInfo association : userAssociations) {
                         if (sanitizeWithCallerChecks(mService.getContext(), association) != null) {
-                            mRevokeProcessor.disassociateInternal(association.getId());
+                            mDisassociationProcessor.disassociate(association.getId());
                         }
                     }
                 }
                 break;
 
-                case "clear-association-memory-cache":
-                    mService.persistState();
-                    mService.loadAssociationsFromDisk();
+                case "refresh-cache":
+                    mAssociationStore.refreshCache();
                     break;
 
                 case "simulate-device-appeared":
diff --git a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
index 75cb120..46d60f9 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
@@ -16,7 +16,6 @@
 
 package com.android.server.companion.association;
 
-import static com.android.internal.util.CollectionUtils.forEach;
 import static com.android.internal.util.XmlUtils.readBooleanAttribute;
 import static com.android.internal.util.XmlUtils.readIntAttribute;
 import static com.android.internal.util.XmlUtils.readLongAttribute;
@@ -26,7 +25,6 @@
 import static com.android.internal.util.XmlUtils.writeLongAttribute;
 import static com.android.internal.util.XmlUtils.writeStringAttribute;
 import static com.android.server.companion.utils.AssociationUtils.getFirstAssociationIdForUser;
-import static com.android.server.companion.utils.AssociationUtils.getLastAssociationIdForUser;
 import static com.android.server.companion.utils.DataStoreUtils.createStorageFileForUser;
 import static com.android.server.companion.utils.DataStoreUtils.fileToByteArray;
 import static com.android.server.companion.utils.DataStoreUtils.isEndOfTag;
@@ -40,10 +38,8 @@
 import android.companion.AssociationInfo;
 import android.net.MacAddress;
 import android.os.Environment;
-import android.util.ArrayMap;
 import android.util.AtomicFile;
 import android.util.Slog;
-import android.util.SparseArray;
 import android.util.Xml;
 
 import com.android.internal.util.XmlUtils;
@@ -59,11 +55,9 @@
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.util.Collection;
-import java.util.HashSet;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 
@@ -82,8 +76,8 @@
  * <p>
  * Before Android T the data was stored using the v0 schema. See:
  * <ul>
- * <li>{@link #readAssociationsV0(TypedXmlPullParser, int, Collection) readAssociationsV0()}.
- * <li>{@link #readAssociationV0(TypedXmlPullParser, int, int, Collection) readAssociationV0()}.
+ * <li>{@link #readAssociationsV0(TypedXmlPullParser, int) readAssociationsV0()}.
+ * <li>{@link #readAssociationV0(TypedXmlPullParser, int, int) readAssociationV0()}.
  * </ul>
  *
  * The following snippet is a sample of a file that is using v0 schema.
@@ -116,15 +110,14 @@
  * optional.
  * <ul>
  * <li> {@link #CURRENT_PERSISTENCE_VERSION}
- * <li> {@link #readAssociationsV1(TypedXmlPullParser, int, Collection) readAssociationsV1()}
- * <li> {@link #readAssociationV1(TypedXmlPullParser, int, Collection) readAssociationV1()}
- * <li> {@link #readPreviouslyUsedIdsV1(TypedXmlPullParser, Map) readPreviouslyUsedIdsV1()}
+ * <li> {@link #readAssociationsV1(TypedXmlPullParser, int) readAssociationsV1()}
+ * <li> {@link #readAssociationV1(TypedXmlPullParser, int) readAssociationV1()}
  * </ul>
  *
  * The following snippet is a sample of a file that is using v1 schema.
  * <pre>{@code
  * <state persistence-version="1">
- *     <associations>
+ *     <associations max-id="3">
  *         <association
  *             id="1"
  *             package="com.sample.companion.app"
@@ -148,18 +141,12 @@
  *             time_approved="1634641160229"
  *             system_data_sync_flags="1"/>
  *     </associations>
- *
- *     <previously-used-ids>
- *         <package package_name="com.sample.companion.app">
- *             <id>2</id>
- *         </package>
- *     </previously-used-ids>
  * </state>
  * }</pre>
  */
 @SuppressLint("LongLogTag")
 public final class AssociationDiskStore {
-    private static final String TAG = "CompanionDevice_AssociationDiskStore";
+    private static final String TAG = "CDM_AssociationDiskStore";
 
     private static final int CURRENT_PERSISTENCE_VERSION = 1;
 
@@ -169,16 +156,11 @@
     private static final String XML_TAG_STATE = "state";
     private static final String XML_TAG_ASSOCIATIONS = "associations";
     private static final String XML_TAG_ASSOCIATION = "association";
-    private static final String XML_TAG_PREVIOUSLY_USED_IDS = "previously-used-ids";
-    private static final String XML_TAG_PACKAGE = "package";
     private static final String XML_TAG_TAG = "tag";
-    private static final String XML_TAG_ID = "id";
 
     private static final String XML_ATTR_PERSISTENCE_VERSION = "persistence-version";
+    private static final String XML_ATTR_MAX_ID = "max-id";
     private static final String XML_ATTR_ID = "id";
-    // Used in <package> elements, nested within <previously-used-ids> elements.
-    private static final String XML_ATTR_PACKAGE_NAME = "package_name";
-    // Used in <association> elements, nested within <associations> elements.
     private static final String XML_ATTR_PACKAGE = "package";
     private static final String XML_ATTR_MAC_ADDRESS = "mac_address";
     private static final String XML_ATTR_DISPLAY_NAME = "display_name";
@@ -199,38 +181,12 @@
     /**
      * Read all associations for given users
      */
-    public void readStateForUsers(@NonNull List<Integer> userIds,
-            @NonNull Set<AssociationInfo> allAssociationsOut,
-            @NonNull SparseArray<Map<String, Set<Integer>>> previouslyUsedIdsPerUserOut) {
+    public Map<Integer, Associations> readAssociationsByUsers(@NonNull List<Integer> userIds) {
+        Map<Integer, Associations> userToAssociationsMap = new HashMap<>();
         for (int userId : userIds) {
-            // Previously used IDs are stored in the "out" collection per-user.
-            final Map<String, Set<Integer>> previouslyUsedIds = new ArrayMap<>();
-
-            // Associations for all users are stored in a single "flat" set: so we read directly
-            // into it.
-            final Set<AssociationInfo> associationsForUser = new HashSet<>();
-            readStateForUser(userId, associationsForUser, previouslyUsedIds);
-
-            // Go through all the associations for the user and check if their IDs are within
-            // the allowed range (for the user).
-            final int firstAllowedId = getFirstAssociationIdForUser(userId);
-            final int lastAllowedId = getLastAssociationIdForUser(userId);
-            for (AssociationInfo association : associationsForUser) {
-                final int id = association.getId();
-                if (id < firstAllowedId || id > lastAllowedId) {
-                    Slog.e(TAG, "Wrong association ID assignment: " + id + ". "
-                            + "Association belongs to u" + userId + " and thus its ID should be "
-                            + "within [" + firstAllowedId + ", " + lastAllowedId + "] range.");
-                    // TODO(b/224736262): try fixing (re-assigning) the ID?
-                }
-            }
-
-            // Add user's association to the "output" set.
-            allAssociationsOut.addAll(associationsForUser);
-
-            // Save previously used IDs for this user into the "out" structure.
-            previouslyUsedIdsPerUserOut.append(userId, previouslyUsedIds);
+            userToAssociationsMap.put(userId, readAssociationsByUser(userId));
         }
+        return userToAssociationsMap;
     }
 
     /**
@@ -240,16 +196,12 @@
      * retrieval from this datastore because it is not persisted (by design). This means that
      * persisted data is not guaranteed to be identical to the initial data that was stored at the
      * time of association.
-     *
-     * @param userId Android UserID
-     * @param associationsOut a container to read the {@link AssociationInfo}s "into".
-     * @param previouslyUsedIdsPerPackageOut a container to read the used IDs "into".
      */
-    private void readStateForUser(@UserIdInt int userId,
-            @NonNull Collection<AssociationInfo> associationsOut,
-            @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) {
-        Slog.i(TAG, "Reading associations for user " + userId + " from disk");
+    @NonNull
+    private Associations readAssociationsByUser(@UserIdInt int userId) {
+        Slog.i(TAG, "Reading associations for user " + userId + " from disk.");
         final AtomicFile file = getStorageFileForUser(userId);
+        Associations associations;
 
         // getStorageFileForUser() ALWAYS returns the SAME OBJECT, which allows us to synchronize
         // accesses to the file on the file system using this AtomicFile object.
@@ -260,7 +212,7 @@
             if (!file.getBaseFile().exists()) {
                 legacyBaseFile = getBaseLegacyStorageFileForUser(userId);
                 if (!legacyBaseFile.exists()) {
-                    return;
+                    return new Associations();
                 }
 
                 readFrom = new AtomicFile(legacyBaseFile);
@@ -270,13 +222,12 @@
                 rootTag = XML_TAG_STATE;
             }
 
-            final int version = readStateFromFileLocked(userId, readFrom, rootTag,
-                    associationsOut, previouslyUsedIdsPerPackageOut);
+            associations = readAssociationsFromFile(userId, readFrom, rootTag);
 
-            if (legacyBaseFile != null || version < CURRENT_PERSISTENCE_VERSION) {
+            if (legacyBaseFile != null || associations.getVersion() < CURRENT_PERSISTENCE_VERSION) {
                 // The data is either in the legacy file or in the legacy format, or both.
                 // Save the data to right file in using the current format.
-                persistStateToFileLocked(file, associationsOut, previouslyUsedIdsPerPackageOut);
+                writeAssociationsToFile(file, associations);
 
                 if (legacyBaseFile != null) {
                     // We saved the data to the right file, can delete the old file now.
@@ -284,89 +235,75 @@
                 }
             }
         }
+        return associations;
     }
 
     /**
-     * Persisted data to the disk.
-     *
-     * Note that associatedDevice field in {@link AssociationInfo} is not persisted by this
-     * datastore implementation.
-     *
-     * @param userId Android UserID
-     * @param associations a set of user's associations.
-     * @param previouslyUsedIdsPerPackage a set previously used Association IDs for the user.
+     * Write associations to disk for the user.
      */
-    public void persistStateForUser(@UserIdInt int userId,
-            @NonNull Collection<AssociationInfo> associations,
-            @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackage) {
+    public void writeAssociationsForUser(@UserIdInt int userId,
+            @NonNull Associations associations) {
         Slog.i(TAG, "Writing associations for user " + userId + " to disk");
 
         final AtomicFile file = getStorageFileForUser(userId);
         // getStorageFileForUser() ALWAYS returns the SAME OBJECT, which allows us to synchronize
         // accesses to the file on the file system using this AtomicFile object.
         synchronized (file) {
-            persistStateToFileLocked(file, associations, previouslyUsedIdsPerPackage);
+            writeAssociationsToFile(file, associations);
         }
     }
 
-    private int readStateFromFileLocked(@UserIdInt int userId, @NonNull AtomicFile file,
-            @NonNull String rootTag, @Nullable Collection<AssociationInfo> associationsOut,
-            @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) {
+    @NonNull
+    private static Associations readAssociationsFromFile(@UserIdInt int userId,
+            @NonNull AtomicFile file, @NonNull String rootTag) {
         try (FileInputStream in = file.openRead()) {
-            return readStateFromInputStream(userId, in, rootTag, associationsOut,
-                    previouslyUsedIdsPerPackageOut);
+            return readAssociationsFromInputStream(userId, in, rootTag);
         } catch (XmlPullParserException | IOException e) {
             Slog.e(TAG, "Error while reading associations file", e);
-            return -1;
+            return new Associations();
         }
     }
 
-    private int readStateFromInputStream(@UserIdInt int userId, @NonNull InputStream in,
-            @NonNull String rootTag, @Nullable Collection<AssociationInfo> associationsOut,
-            @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut)
+    @NonNull
+    private static Associations readAssociationsFromInputStream(@UserIdInt int userId,
+            @NonNull InputStream in, @NonNull String rootTag)
             throws XmlPullParserException, IOException {
         final TypedXmlPullParser parser = Xml.resolvePullParser(in);
-
         XmlUtils.beginDocument(parser, rootTag);
+
         final int version = readIntAttribute(parser, XML_ATTR_PERSISTENCE_VERSION, 0);
+        Associations associations = new Associations();
+
         switch (version) {
             case 0:
-                readAssociationsV0(parser, userId, associationsOut);
+                associations = readAssociationsV0(parser, userId);
                 break;
             case 1:
                 while (true) {
                     parser.nextTag();
                     if (isStartOfTag(parser, XML_TAG_ASSOCIATIONS)) {
-                        readAssociationsV1(parser, userId, associationsOut);
-                    } else if (isStartOfTag(parser, XML_TAG_PREVIOUSLY_USED_IDS)) {
-                        readPreviouslyUsedIdsV1(parser, previouslyUsedIdsPerPackageOut);
+                        associations = readAssociationsV1(parser, userId);
                     } else if (isEndOfTag(parser, rootTag)) {
                         break;
                     }
                 }
                 break;
         }
-        return version;
+        return associations;
     }
 
-    private void persistStateToFileLocked(@NonNull AtomicFile file,
-            @Nullable Collection<AssociationInfo> associations,
-            @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackage) {
+    private void writeAssociationsToFile(@NonNull AtomicFile file,
+            @NonNull Associations associations) {
         // Writing to file could fail, for example, if the user has been recently removed and so was
         // their DE (/data/system_de/<user-id>/) directory.
         writeToFileSafely(file, out -> {
             final TypedXmlSerializer serializer = Xml.resolveSerializer(out);
-            serializer.setFeature(
-                    "http://xmlpull.org/v1/doc/features.html#indent-output", true);
-
+            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
             serializer.startDocument(null, true);
             serializer.startTag(null, XML_TAG_STATE);
             writeIntAttribute(serializer,
                     XML_ATTR_PERSISTENCE_VERSION, CURRENT_PERSISTENCE_VERSION);
-
             writeAssociations(serializer, associations);
-            writePreviouslyUsedIds(serializer, previouslyUsedIdsPerPackage);
-
             serializer.endTag(null, XML_TAG_STATE);
             serializer.endDocument();
         });
@@ -379,7 +316,8 @@
      * IMPORTANT: the method will ALWAYS return the same {@link AtomicFile} object, which makes it
      * possible to synchronize reads and writes to the file using the returned object.
      */
-    private @NonNull AtomicFile getStorageFileForUser(@UserIdInt int userId) {
+    @NonNull
+    private AtomicFile getStorageFileForUser(@UserIdInt int userId) {
         return mUserIdToStorageFile.computeIfAbsent(userId,
                 u -> createStorageFileForUser(userId, FILE_NAME));
     }
@@ -399,14 +337,12 @@
     /**
      * Convert payload to a set of associations
      */
-    public void readStateFromPayload(byte[] payload, @UserIdInt int userId,
-                              @NonNull Set<AssociationInfo> associationsOut,
-                              @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) {
+    public static Associations readAssociationsFromPayload(byte[] payload, @UserIdInt int userId) {
         try (ByteArrayInputStream in = new ByteArrayInputStream(payload)) {
-            readStateFromInputStream(userId, in, XML_TAG_STATE, associationsOut,
-                    previouslyUsedIdsPerPackageOut);
+            return readAssociationsFromInputStream(userId, in, XML_TAG_STATE);
         } catch (XmlPullParserException | IOException e) {
             Slog.e(TAG, "Error while reading associations file", e);
+            return new Associations();
         }
     }
 
@@ -414,8 +350,8 @@
         return new File(Environment.getUserSystemDirectory(userId), FILE_NAME_LEGACY);
     }
 
-    private static void readAssociationsV0(@NonNull TypedXmlPullParser parser,
-            @UserIdInt int userId, @NonNull Collection<AssociationInfo> out)
+    private static Associations readAssociationsV0(@NonNull TypedXmlPullParser parser,
+            @UserIdInt int userId)
             throws XmlPullParserException, IOException {
         requireStartOfTag(parser, XML_TAG_ASSOCIATIONS);
 
@@ -426,52 +362,70 @@
         // means that CDM hasn't assigned any IDs yet, so we can just start from the first available
         // id for each user (eg. 1 for user 0; 100 001 - for user 1; 200 001 - for user 2; etc).
         int associationId = getFirstAssociationIdForUser(userId);
+        Associations associations = new Associations();
+        associations.setVersion(0);
+
         while (true) {
             parser.nextTag();
             if (isEndOfTag(parser, XML_TAG_ASSOCIATIONS)) break;
             if (!isStartOfTag(parser, XML_TAG_ASSOCIATION)) continue;
 
-            readAssociationV0(parser, userId, associationId++, out);
+            associations.addAssociation(readAssociationV0(parser, userId, associationId++));
         }
+
+        associations.setMaxId(associationId - 1);
+
+        return associations;
     }
 
-    private static void readAssociationV0(@NonNull TypedXmlPullParser parser, @UserIdInt int userId,
-            int associationId, @NonNull Collection<AssociationInfo> out)
+    private static AssociationInfo readAssociationV0(@NonNull TypedXmlPullParser parser,
+            @UserIdInt int userId, int associationId)
             throws XmlPullParserException {
         requireStartOfTag(parser, XML_TAG_ASSOCIATION);
 
         final String appPackage = readStringAttribute(parser, XML_ATTR_PACKAGE);
         final String tag = readStringAttribute(parser, XML_TAG_TAG);
         final String deviceAddress = readStringAttribute(parser, LEGACY_XML_ATTR_DEVICE);
-
-        if (appPackage == null || deviceAddress == null) return;
-
         final String profile = readStringAttribute(parser, XML_ATTR_PROFILE);
         final boolean notify = readBooleanAttribute(parser, XML_ATTR_NOTIFY_DEVICE_NEARBY);
         final long timeApproved = readLongAttribute(parser, XML_ATTR_TIME_APPROVED, 0L);
 
-        out.add(new AssociationInfo(associationId, userId, appPackage, tag,
+        return new AssociationInfo(associationId, userId, appPackage, tag,
                 MacAddress.fromString(deviceAddress), null, profile, null,
                 /* managedByCompanionApp */ false, notify, /* revoked */ false, /* pending */ false,
-                timeApproved, Long.MAX_VALUE, /* systemDataSyncFlags */ 0));
+                timeApproved, Long.MAX_VALUE, /* systemDataSyncFlags */ 0);
     }
 
-    private static void readAssociationsV1(@NonNull TypedXmlPullParser parser,
-            @UserIdInt int userId, @NonNull Collection<AssociationInfo> out)
+    private static Associations readAssociationsV1(@NonNull TypedXmlPullParser parser,
+            @UserIdInt int userId)
             throws XmlPullParserException, IOException {
         requireStartOfTag(parser, XML_TAG_ASSOCIATIONS);
 
+        // For old builds that don't have max-id attr,
+        // default maxId to 0 and get the maxId out of all association ids.
+        int maxId = readIntAttribute(parser, XML_ATTR_MAX_ID, 0);
+        Associations associations = new Associations();
+        associations.setVersion(1);
+
         while (true) {
             parser.nextTag();
             if (isEndOfTag(parser, XML_TAG_ASSOCIATIONS)) break;
             if (!isStartOfTag(parser, XML_TAG_ASSOCIATION)) continue;
 
-            readAssociationV1(parser, userId, out);
+            AssociationInfo association = readAssociationV1(parser, userId);
+            associations.addAssociation(association);
+
+            maxId = Math.max(maxId, association.getId());
         }
+
+        associations.setMaxId(maxId);
+
+        return associations;
     }
 
-    private static void readAssociationV1(@NonNull TypedXmlPullParser parser, @UserIdInt int userId,
-            @NonNull Collection<AssociationInfo> out) throws XmlPullParserException, IOException {
+    private static AssociationInfo readAssociationV1(@NonNull TypedXmlPullParser parser,
+            @UserIdInt int userId)
+            throws XmlPullParserException, IOException {
         requireStartOfTag(parser, XML_TAG_ASSOCIATION);
 
         final int associationId = readIntAttribute(parser, XML_ATTR_ID);
@@ -491,46 +445,19 @@
         final int systemDataSyncFlags = readIntAttribute(parser,
                 XML_ATTR_SYSTEM_DATA_SYNC_FLAGS, 0);
 
-        final AssociationInfo associationInfo = createAssociationInfoNoThrow(associationId, userId,
-                appPackage, tag, macAddress, displayName, profile, selfManaged, notify, revoked,
-                pending, timeApproved, lastTimeConnected, systemDataSyncFlags);
-        if (associationInfo != null) {
-            out.add(associationInfo);
-        }
-    }
-
-    private static void readPreviouslyUsedIdsV1(@NonNull TypedXmlPullParser parser,
-            @NonNull Map<String, Set<Integer>> out) throws XmlPullParserException, IOException {
-        requireStartOfTag(parser, XML_TAG_PREVIOUSLY_USED_IDS);
-
-        while (true) {
-            parser.nextTag();
-            if (isEndOfTag(parser, XML_TAG_PREVIOUSLY_USED_IDS)) break;
-            if (!isStartOfTag(parser, XML_TAG_PACKAGE)) continue;
-
-            final String packageName = readStringAttribute(parser, XML_ATTR_PACKAGE_NAME);
-            final Set<Integer> usedIds = new HashSet<>();
-
-            while (true) {
-                parser.nextTag();
-                if (isEndOfTag(parser, XML_TAG_PACKAGE)) break;
-                if (!isStartOfTag(parser, XML_TAG_ID)) continue;
-
-                parser.nextToken();
-                final int id = Integer.parseInt(parser.getText());
-                usedIds.add(id);
-            }
-
-            out.put(packageName, usedIds);
-        }
+        return new AssociationInfo(associationId, userId, appPackage, tag, macAddress, displayName,
+                profile, null, selfManaged, notify, revoked, pending, timeApproved,
+                lastTimeConnected, systemDataSyncFlags);
     }
 
     private static void writeAssociations(@NonNull XmlSerializer parent,
-            @Nullable Collection<AssociationInfo> associations) throws IOException {
+            @NonNull Associations associations)
+            throws IOException {
         final XmlSerializer serializer = parent.startTag(null, XML_TAG_ASSOCIATIONS);
-        for (AssociationInfo association : associations) {
+        for (AssociationInfo association : associations.getAssociations()) {
             writeAssociation(serializer, association);
         }
+        writeIntAttribute(serializer, XML_ATTR_MAX_ID, associations.getMaxId());
         serializer.endTag(null, XML_TAG_ASSOCIATIONS);
     }
 
@@ -557,26 +484,6 @@
         serializer.endTag(null, XML_TAG_ASSOCIATION);
     }
 
-    private static void writePreviouslyUsedIds(@NonNull XmlSerializer parent,
-            @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackage) throws IOException {
-        final XmlSerializer serializer = parent.startTag(null, XML_TAG_PREVIOUSLY_USED_IDS);
-        for (Map.Entry<String, Set<Integer>> entry : previouslyUsedIdsPerPackage.entrySet()) {
-            writePreviouslyUsedIdsForPackage(serializer, entry.getKey(), entry.getValue());
-        }
-        serializer.endTag(null, XML_TAG_PREVIOUSLY_USED_IDS);
-    }
-
-    private static void writePreviouslyUsedIdsForPackage(@NonNull XmlSerializer parent,
-            @NonNull String packageName, @NonNull Set<Integer> previouslyUsedIds)
-            throws IOException {
-        final XmlSerializer serializer = parent.startTag(null, XML_TAG_PACKAGE);
-        writeStringAttribute(serializer, XML_ATTR_PACKAGE_NAME, packageName);
-        forEach(previouslyUsedIds, id -> serializer.startTag(null, XML_TAG_ID)
-                .text(Integer.toString(id))
-                .endTag(null, XML_TAG_ID));
-        serializer.endTag(null, XML_TAG_PACKAGE);
-    }
-
     private static void requireStartOfTag(@NonNull XmlPullParser parser, @NonNull String tag)
             throws XmlPullParserException {
         if (isStartOfTag(parser, tag)) return;
@@ -587,22 +494,4 @@
     private static @Nullable MacAddress stringToMacAddress(@Nullable String address) {
         return address != null ? MacAddress.fromString(address) : null;
     }
-
-    private static AssociationInfo createAssociationInfoNoThrow(int associationId,
-            @UserIdInt int userId, @NonNull String appPackage, @Nullable String tag,
-            @Nullable MacAddress macAddress, @Nullable CharSequence displayName,
-            @Nullable String profile, boolean selfManaged, boolean notify, boolean revoked,
-            boolean pending, long timeApproved, long lastTimeConnected, int systemDataSyncFlags) {
-        AssociationInfo associationInfo = null;
-        try {
-            // We do not persist AssociatedDevice, which means that AssociationInfo retrieved from
-            // datastore is not guaranteed to be identical to the one from initial association.
-            associationInfo = new AssociationInfo(associationId, userId, appPackage, tag,
-                    macAddress, displayName, profile, null, selfManaged, notify,
-                    revoked, pending, timeApproved, lastTimeConnected, systemDataSyncFlags);
-        } catch (Exception e) {
-            Slog.e(TAG, "Could not create AssociationInfo", e);
-        }
-        return associationInfo;
-    }
 }
diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index 29ec7c2..a02d9f9 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -24,7 +24,6 @@
 import static android.content.ComponentName.createRelative;
 import static android.content.pm.PackageManager.FEATURE_WATCH;
 
-import static com.android.server.companion.utils.MetricUtils.logCreateAssociation;
 import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature;
 import static com.android.server.companion.utils.PermissionsUtils.enforcePermissionForCreatingAssociation;
 import static com.android.server.companion.utils.RolesUtils.addRoleHolderForAssociation;
@@ -128,17 +127,16 @@
     private static final long ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS = 60 * 60 * 1000; // 60 min;
 
     private final @NonNull Context mContext;
-    private final @NonNull CompanionDeviceManagerService mService;
-    private final @NonNull PackageManagerInternal mPackageManager;
+    private final @NonNull PackageManagerInternal mPackageManagerInternal;
     private final @NonNull AssociationStore mAssociationStore;
     @NonNull
     private final ComponentName mCompanionDeviceActivity;
 
-    public AssociationRequestsProcessor(@NonNull CompanionDeviceManagerService service,
+    public AssociationRequestsProcessor(@NonNull Context context,
+            @NonNull PackageManagerInternal packageManagerInternal,
             @NonNull AssociationStore associationStore) {
-        mContext = service.getContext();
-        mService = service;
-        mPackageManager = service.mPackageManagerInternal;
+        mContext = context;
+        mPackageManagerInternal = packageManagerInternal;
         mAssociationStore = associationStore;
         mCompanionDeviceActivity = createRelative(
                 mContext.getString(R.string.config_companionDeviceManagerPackage),
@@ -160,7 +158,7 @@
         requireNonNull(packageName, "Package name MUST NOT be null");
         requireNonNull(callback, "Callback MUST NOT be null");
 
-        final int packageUid = mPackageManager.getPackageUid(packageName, 0, userId);
+        final int packageUid = mPackageManagerInternal.getPackageUid(packageName, 0, userId);
         Slog.d(TAG, "processNewAssociationRequest() " + "request=" + request + ", " + "package=u"
                 + userId + "/" + packageName + " (uid=" + packageUid + ")");
 
@@ -226,7 +224,7 @@
 
         enforceUsesCompanionDeviceFeature(mContext, userId, packageName);
 
-        final int packageUid = mPackageManager.getPackageUid(packageName, 0, userId);
+        final int packageUid = mPackageManagerInternal.getPackageUid(packageName, 0, userId);
 
         final Bundle extras = new Bundle();
         extras.putBoolean(EXTRA_FORCE_CANCEL_CONFIRMATION, true);
@@ -243,7 +241,7 @@
             @NonNull ResultReceiver resultReceiver, @Nullable MacAddress macAddress) {
         final String packageName = request.getPackageName();
         final int userId = request.getUserId();
-        final int packageUid = mPackageManager.getPackageUid(packageName, 0, userId);
+        final int packageUid = mPackageManagerInternal.getPackageUid(packageName, 0, userId);
 
         // 1. Need to check permissions again in case something changed, since we first received
         // this request.
@@ -267,15 +265,12 @@
             @NonNull AssociationRequest request, @NonNull String packageName, @UserIdInt int userId,
             @Nullable MacAddress macAddress, @NonNull IAssociationRequestCallback callback,
             @NonNull ResultReceiver resultReceiver) {
-        final long callingIdentity = Binder.clearCallingIdentity();
-        try {
+        Binder.withCleanCallingIdentity(() -> {
             createAssociation(userId, packageName, macAddress, request.getDisplayName(),
                     request.getDeviceProfile(), request.getAssociatedDevice(),
                     request.isSelfManaged(),
                     callback, resultReceiver);
-        } finally {
-            Binder.restoreCallingIdentity(callingIdentity);
-        }
+        });
     }
 
     /**
@@ -286,7 +281,7 @@
             @Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice,
             boolean selfManaged, @Nullable IAssociationRequestCallback callback,
             @Nullable ResultReceiver resultReceiver) {
-        final int id = mService.getNewAssociationIdForPackage(userId, packageName);
+        final int id = mAssociationStore.getNextId(userId);
         final long timestamp = System.currentTimeMillis();
 
         final AssociationInfo association = new AssociationInfo(id, userId, packageName,
@@ -296,10 +291,6 @@
 
         // Add role holder for association (if specified) and add new association to store.
         maybeGrantRoleAndStoreAssociation(association, callback, resultReceiver);
-
-        // Don't need to update the mRevokedAssociationsPendingRoleHolderRemoval since
-        // maybeRemoveRoleHolderForAssociation in PackageInactivityListener will handle the case
-        // that there are other devices with the same profile, so the role holder won't be removed.
     }
 
     /**
@@ -311,12 +302,12 @@
         // If the "Device Profile" is specified, make the companion application a holder of the
         // corresponding role.
         // If it is null, then the operation will succeed without granting any role.
-        addRoleHolderForAssociation(mService.getContext(), association, success -> {
+        addRoleHolderForAssociation(mContext, association, success -> {
             if (success) {
                 Slog.i(TAG, "Added " + association.getDeviceProfile() + " role to userId="
                         + association.getUserId() + ", packageName="
                         + association.getPackageName());
-                addAssociationToStore(association);
+                mAssociationStore.addAssociation(association);
                 sendCallbackAndFinish(association, callback, resultReceiver);
             } else {
                 Slog.e(TAG, "Failed to add u" + association.getUserId()
@@ -347,17 +338,6 @@
         mAssociationStore.updateAssociation(updated);
     }
 
-    private void addAssociationToStore(@NonNull AssociationInfo association) {
-        Slog.i(TAG, "New CDM association created=" + association);
-
-        mAssociationStore.addAssociation(association);
-
-        mService.updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(),
-                association.getPackageName());
-
-        logCreateAssociation(association.getDeviceProfile());
-    }
-
     private void sendCallbackAndFinish(@Nullable AssociationInfo association,
             @Nullable IAssociationRequestCallback callback,
             @Nullable ResultReceiver resultReceiver) {
@@ -409,27 +389,22 @@
 
     private PendingIntent createPendingIntent(int packageUid, Intent intent) {
         final PendingIntent pendingIntent;
-        final long token = Binder.clearCallingIdentity();
 
         // Using uid of the application that will own the association (usually the same
         // application that sent the request) allows us to have multiple "pending" association
         // requests at the same time.
         // If the application already has a pending association request, that PendingIntent
         // will be cancelled except application wants to cancel the request by the system.
-        try {
-            pendingIntent = PendingIntent.getActivityAsUser(
+        return Binder.withCleanCallingIdentity(() ->
+            PendingIntent.getActivityAsUser(
                     mContext, /*requestCode */ packageUid, intent,
                     FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE,
                     ActivityOptions.makeBasic()
                             .setPendingIntentCreatorBackgroundActivityStartMode(
                                     ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
                             .toBundle(),
-                    UserHandle.CURRENT);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-
-        return pendingIntent;
+                    UserHandle.CURRENT)
+        );
     }
 
     private final ResultReceiver mOnRequestConfirmationReceiver =
@@ -470,7 +445,7 @@
         // Throttle frequent associations
         final long now = System.currentTimeMillis();
         final List<AssociationInfo> associationForPackage =
-                mAssociationStore.getAssociationsForPackage(userId, packageName);
+                mAssociationStore.getActiveAssociationsByPackage(userId, packageName);
         // Number of "recent" associations.
         int recent = 0;
         for (AssociationInfo association : associationForPackage) {
@@ -486,6 +461,6 @@
             }
         }
 
-        return PackageUtils.isPackageAllowlisted(mContext, mPackageManager, packageName);
+        return PackageUtils.isPackageAllowlisted(mContext, mPackageManagerInternal, packageName);
     }
 }
diff --git a/services/companion/java/com/android/server/companion/association/AssociationRevokeProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRevokeProcessor.java
deleted file mode 100644
index d1efbbc..0000000
--- a/services/companion/java/com/android/server/companion/association/AssociationRevokeProcessor.java
+++ /dev/null
@@ -1,383 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.companion.association;
-
-import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
-import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
-
-import static com.android.internal.util.CollectionUtils.any;
-import static com.android.server.companion.utils.MetricUtils.logRemoveAssociation;
-import static com.android.server.companion.utils.RolesUtils.removeRoleHolderForAssociation;
-import static com.android.server.companion.CompanionDeviceManagerService.PerUserAssociationSet;
-
-import android.annotation.NonNull;
-import android.annotation.SuppressLint;
-import android.annotation.UserIdInt;
-import android.app.ActivityManager;
-import android.companion.AssociationInfo;
-import android.content.Context;
-import android.content.pm.PackageManagerInternal;
-import android.os.Binder;
-import android.os.UserHandle;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.server.companion.CompanionApplicationController;
-import com.android.server.companion.CompanionDeviceManagerService;
-import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
-import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
-import com.android.server.companion.transport.CompanionTransportManager;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * A class response for Association removal.
- */
-@SuppressLint("LongLogTag")
-public class AssociationRevokeProcessor {
-
-    private static final String TAG = "CDM_AssociationRevokeProcessor";
-    private static final boolean DEBUG = false;
-    private final @NonNull Context mContext;
-    private final @NonNull CompanionDeviceManagerService mService;
-    private final @NonNull AssociationStore mAssociationStore;
-    private final @NonNull PackageManagerInternal mPackageManagerInternal;
-    private final @NonNull CompanionDevicePresenceMonitor mDevicePresenceMonitor;
-    private final @NonNull SystemDataTransferRequestStore mSystemDataTransferRequestStore;
-    private final @NonNull CompanionApplicationController mCompanionAppController;
-    private final @NonNull CompanionTransportManager mTransportManager;
-    private final OnPackageVisibilityChangeListener mOnPackageVisibilityChangeListener;
-    private final ActivityManager mActivityManager;
-
-    /**
-     * A structure that consists of a set of revoked associations that pending for role holder
-     * removal per each user.
-     *
-     * @see #maybeRemoveRoleHolderForAssociation(AssociationInfo)
-     * @see #addToPendingRoleHolderRemoval(AssociationInfo)
-     * @see #removeFromPendingRoleHolderRemoval(AssociationInfo)
-     * @see #getPendingRoleHolderRemovalAssociationsForUser(int)
-     */
-    @GuardedBy("mRevokedAssociationsPendingRoleHolderRemoval")
-    private final PerUserAssociationSet mRevokedAssociationsPendingRoleHolderRemoval =
-            new PerUserAssociationSet();
-    /**
-     * Contains uid-s of packages pending to be removed from the role holder list (after
-     * revocation of an association), which will happen one the package is no longer visible to the
-     * user.
-     * For quicker uid -> (userId, packageName) look-up this is not a {@code Set<Integer>} but
-     * a {@code Map<Integer, String>} which maps uid-s to packageName-s (userId-s can be derived
-     * from uid-s using {@link UserHandle#getUserId(int)}).
-     *
-     * @see #maybeRemoveRoleHolderForAssociation(AssociationInfo)
-     * @see #addToPendingRoleHolderRemoval(AssociationInfo)
-     * @see #removeFromPendingRoleHolderRemoval(AssociationInfo)
-     */
-    @GuardedBy("mRevokedAssociationsPendingRoleHolderRemoval")
-    private final Map<Integer, String> mUidsPendingRoleHolderRemoval = new HashMap<>();
-
-    public AssociationRevokeProcessor(@NonNull CompanionDeviceManagerService service,
-            @NonNull AssociationStore associationStore,
-            @NonNull PackageManagerInternal packageManager,
-            @NonNull CompanionDevicePresenceMonitor devicePresenceMonitor,
-            @NonNull CompanionApplicationController applicationController,
-            @NonNull SystemDataTransferRequestStore systemDataTransferRequestStore,
-            @NonNull CompanionTransportManager companionTransportManager) {
-        mService = service;
-        mContext = service.getContext();
-        mActivityManager = mContext.getSystemService(ActivityManager.class);
-        mAssociationStore = associationStore;
-        mPackageManagerInternal = packageManager;
-        mOnPackageVisibilityChangeListener =
-                new OnPackageVisibilityChangeListener(mActivityManager);
-        mDevicePresenceMonitor = devicePresenceMonitor;
-        mCompanionAppController = applicationController;
-        mSystemDataTransferRequestStore = systemDataTransferRequestStore;
-        mTransportManager = companionTransportManager;
-    }
-
-    /**
-     * Disassociate an association
-     */
-    // TODO: also revoke notification access
-    public void disassociateInternal(int associationId) {
-        final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
-        final int userId = association.getUserId();
-        final String packageName = association.getPackageName();
-        final String deviceProfile = association.getDeviceProfile();
-
-        // Detach transport if exists
-        mTransportManager.detachSystemDataTransport(packageName, userId, associationId);
-
-        if (!maybeRemoveRoleHolderForAssociation(association)) {
-            // Need to remove the app from list of the role holders, but will have to do it later
-            // (the app is in foreground at the moment).
-            addToPendingRoleHolderRemoval(association);
-        }
-
-        // Need to check if device still present now because CompanionDevicePresenceMonitor will
-        // remove current connected device after mAssociationStore.removeAssociation
-        final boolean wasPresent = mDevicePresenceMonitor.isDevicePresent(associationId);
-
-        // Removing the association.
-        mAssociationStore.removeAssociation(associationId);
-        // Do not need to persistUserState since CompanionDeviceManagerService will get callback
-        // from #onAssociationChanged, and it will handle the persistUserState which including
-        // active and revoked association.
-        logRemoveAssociation(deviceProfile);
-
-        // Remove all the system data transfer requests for the association.
-        mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, associationId);
-
-        if (!wasPresent || !association.isNotifyOnDeviceNearby()) return;
-        // The device was connected and the app was notified: check if we need to unbind the app
-        // now.
-        final boolean shouldStayBound = any(
-                mAssociationStore.getAssociationsForPackage(userId, packageName),
-                it -> it.isNotifyOnDeviceNearby()
-                        && mDevicePresenceMonitor.isDevicePresent(it.getId()));
-        if (shouldStayBound) return;
-        mCompanionAppController.unbindCompanionApplication(userId, packageName);
-    }
-
-    /**
-     * First, checks if the companion application should be removed from the list role holders when
-     * upon association's removal, i.e.: association's profile (matches the role) is not null,
-     * the application does not have other associations with the same profile, etc.
-     *
-     * <p>
-     * Then, if establishes that the application indeed has to be removed from the list of the role
-     * holders, checks if it could be done right now -
-     * {@link android.app.role.RoleManager#removeRoleHolderAsUser(String, String, int, UserHandle, java.util.concurrent.Executor, java.util.function.Consumer) RoleManager#removeRoleHolderAsUser()}
-     * will kill the application's process, which leads poor user experience if the application was
-     * in foreground when this happened, to avoid this CDMS delays invoking
-     * {@code RoleManager.removeRoleHolderAsUser()} until the app is no longer in foreground.
-     *
-     * @return {@code true} if the application does NOT need be removed from the list of the role
-     *         holders OR if the application was successfully removed from the list of role holders.
-     *         I.e.: from the role-management perspective the association is done with.
-     *         {@code false} if the application needs to be removed from the list of role the role
-     *         holders, BUT it CDMS would prefer to do it later.
-     *         I.e.: application is in the foreground at the moment, but invoking
-     *         {@code RoleManager.removeRoleHolderAsUser()} will kill the application's process,
-     *         which would lead to the poor UX, hence need to try later.
-     */
-    public boolean maybeRemoveRoleHolderForAssociation(@NonNull AssociationInfo association) {
-        if (DEBUG) Log.d(TAG, "maybeRemoveRoleHolderForAssociation() association=" + association);
-        final String deviceProfile = association.getDeviceProfile();
-
-        if (deviceProfile == null) {
-            // No role was granted to for this association, there is nothing else we need to here.
-            return true;
-        }
-        // Do not need to remove the system role since it was pre-granted by the system.
-        if (deviceProfile.equals(DEVICE_PROFILE_AUTOMOTIVE_PROJECTION)) {
-            return true;
-        }
-
-        // Check if the applications is associated with another devices with the profile. If so,
-        // it should remain the role holder.
-        final int id = association.getId();
-        final int userId = association.getUserId();
-        final String packageName = association.getPackageName();
-        final boolean roleStillInUse = any(
-                mAssociationStore.getAssociationsForPackage(userId, packageName),
-                it -> deviceProfile.equals(it.getDeviceProfile()) && id != it.getId());
-        if (roleStillInUse) {
-            // Application should remain a role holder, there is nothing else we need to here.
-            return true;
-        }
-
-        final int packageProcessImportance = getPackageProcessImportance(userId, packageName);
-        if (packageProcessImportance <= IMPORTANCE_VISIBLE) {
-            // Need to remove the app from the list of role holders, but the process is visible to
-            // the user at the moment, so we'll need to it later: log and return false.
-            Slog.i(TAG, "Cannot remove role holder for the removed association id=" + id
-                    + " now - process is visible.");
-            return false;
-        }
-
-        removeRoleHolderForAssociation(mContext, association.getUserId(),
-                association.getPackageName(), association.getDeviceProfile());
-        return true;
-    }
-
-    /**
-     * Set revoked flag for active association and add the revoked association and the uid into
-     * the caches.
-     *
-     * @see #mRevokedAssociationsPendingRoleHolderRemoval
-     * @see #mUidsPendingRoleHolderRemoval
-     * @see OnPackageVisibilityChangeListener
-     */
-    public void addToPendingRoleHolderRemoval(@NonNull AssociationInfo association) {
-        // First: set revoked flag
-        association = (new AssociationInfo.Builder(association)).setRevoked(true).build();
-        final String packageName = association.getPackageName();
-        final int userId = association.getUserId();
-        final int uid = mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId);
-        // Second: add to the set.
-        synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
-            mRevokedAssociationsPendingRoleHolderRemoval.forUser(association.getUserId())
-                    .add(association);
-            if (!mUidsPendingRoleHolderRemoval.containsKey(uid)) {
-                mUidsPendingRoleHolderRemoval.put(uid, packageName);
-
-                if (mUidsPendingRoleHolderRemoval.size() == 1) {
-                    // Just added first uid: start the listener
-                    mOnPackageVisibilityChangeListener.startListening();
-                }
-            }
-        }
-    }
-
-    /**
-     * @return a copy of the revoked associations set (safeguarding against
-     *         {@code ConcurrentModificationException}-s).
-     */
-    @NonNull
-    public Set<AssociationInfo> getPendingRoleHolderRemovalAssociationsForUser(
-            @UserIdInt int userId) {
-        synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
-            // Return a copy.
-            return new ArraySet<>(mRevokedAssociationsPendingRoleHolderRemoval.forUser(userId));
-        }
-    }
-
-    @SuppressLint("MissingPermission")
-    private int  getPackageProcessImportance(@UserIdInt int userId, @NonNull String packageName) {
-        return Binder.withCleanCallingIdentity(() -> {
-            final int uid =
-                    mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId);
-            return mActivityManager.getUidImportance(uid);
-        });
-    }
-
-    /**
-     * Remove the revoked association from the cache and also remove the uid from the map if
-     * there are other associations with the same package still pending for role holder removal.
-     *
-     * @see #mRevokedAssociationsPendingRoleHolderRemoval
-     * @see #mUidsPendingRoleHolderRemoval
-     * @see OnPackageVisibilityChangeListener
-     */
-    private void removeFromPendingRoleHolderRemoval(@NonNull AssociationInfo association) {
-        final String packageName = association.getPackageName();
-        final int userId = association.getUserId();
-        final int uid = mPackageManagerInternal.getPackageUid(packageName, /* flags */  0, userId);
-
-        synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
-            mRevokedAssociationsPendingRoleHolderRemoval.forUser(userId)
-                    .remove(association);
-
-            final boolean shouldKeepUidForRemoval = any(
-                    getPendingRoleHolderRemovalAssociationsForUser(userId),
-                    ai -> packageName.equals(ai.getPackageName()));
-            // Do not remove the uid from the map since other associations with
-            // the same packageName still pending for role holder removal.
-            if (!shouldKeepUidForRemoval) {
-                mUidsPendingRoleHolderRemoval.remove(uid);
-            }
-
-            if (mUidsPendingRoleHolderRemoval.isEmpty()) {
-                // The set is empty now - can "turn off" the listener.
-                mOnPackageVisibilityChangeListener.stopListening();
-            }
-        }
-    }
-
-    private String getPackageNameByUid(int uid) {
-        synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
-            return mUidsPendingRoleHolderRemoval.get(uid);
-        }
-    }
-
-    /**
-     * An OnUidImportanceListener class which watches the importance of the packages.
-     * In this class, we ONLY interested in the importance of the running process is greater than
-     * {@link ActivityManager.RunningAppProcessInfo#IMPORTANCE_VISIBLE} for the uids have been added
-     * into the {@link #mUidsPendingRoleHolderRemoval}. Lastly remove the role holder for the
-     * revoked associations for the same packages.
-     *
-     * @see #maybeRemoveRoleHolderForAssociation(AssociationInfo)
-     * @see #removeFromPendingRoleHolderRemoval(AssociationInfo)
-     * @see #getPendingRoleHolderRemovalAssociationsForUser(int)
-     */
-    private class OnPackageVisibilityChangeListener implements
-            ActivityManager.OnUidImportanceListener {
-        final @NonNull ActivityManager mAm;
-
-        OnPackageVisibilityChangeListener(@NonNull ActivityManager am) {
-            this.mAm = am;
-        }
-
-        @SuppressLint("MissingPermission")
-        void startListening() {
-            Binder.withCleanCallingIdentity(
-                    () -> mAm.addOnUidImportanceListener(
-                            /* listener */ OnPackageVisibilityChangeListener.this,
-                            ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE));
-        }
-
-        @SuppressLint("MissingPermission")
-        void stopListening() {
-            Binder.withCleanCallingIdentity(
-                    () -> mAm.removeOnUidImportanceListener(
-                            /* listener */ OnPackageVisibilityChangeListener.this));
-        }
-
-        @Override
-        public void onUidImportance(int uid, int importance) {
-            if (importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE) {
-                // The lower the importance value the more "important" the process is.
-                // We are only interested when the process ceases to be visible.
-                return;
-            }
-
-            final String packageName = getPackageNameByUid(uid);
-            if (packageName == null) {
-                // Not interested in this uid.
-                return;
-            }
-
-            final int userId = UserHandle.getUserId(uid);
-
-            boolean needToPersistStateForUser = false;
-
-            for (AssociationInfo association :
-                    getPendingRoleHolderRemovalAssociationsForUser(userId)) {
-                if (!packageName.equals(association.getPackageName())) continue;
-
-                if (!maybeRemoveRoleHolderForAssociation(association)) {
-                    // Did not remove the role holder, will have to try again later.
-                    continue;
-                }
-
-                removeFromPendingRoleHolderRemoval(association);
-                needToPersistStateForUser = true;
-            }
-
-            if (needToPersistStateForUser) {
-                mService.postPersistUserState(userId);
-            }
-        }
-    }
-}
diff --git a/services/companion/java/com/android/server/companion/association/AssociationStore.java b/services/companion/java/com/android/server/companion/association/AssociationStore.java
index 2f94bde..29de764 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationStore.java
@@ -16,15 +16,24 @@
 
 package com.android.server.companion.association;
 
+import static com.android.server.companion.utils.MetricUtils.logCreateAssociation;
+import static com.android.server.companion.utils.MetricUtils.logRemoveAssociation;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.UserIdInt;
 import android.companion.AssociationInfo;
+import android.companion.IOnAssociationsChangedListener;
+import android.content.pm.UserInfo;
 import android.net.MacAddress;
+import android.os.Binder;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.Slog;
-import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.CollectionUtils;
@@ -33,15 +42,14 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 /**
  * Association store for CRUD.
@@ -109,44 +117,105 @@
 
     private final Object mLock = new Object();
 
-    @GuardedBy("mLock")
-    private final Map<Integer, AssociationInfo> mIdMap = new HashMap<>();
-    @GuardedBy("mLock")
-    private final Map<MacAddress, Set<Integer>> mAddressMap = new HashMap<>();
-    @GuardedBy("mLock")
-    private final SparseArray<List<AssociationInfo>> mCachedPerUser = new SparseArray<>();
+    private final ExecutorService mExecutor;
 
-    @GuardedBy("mListeners")
-    private final Set<OnChangeListener> mListeners = new LinkedHashSet<>();
+    @GuardedBy("mLock")
+    private boolean mPersisted = false;
+    @GuardedBy("mLock")
+    private final Map<Integer, AssociationInfo> mIdToAssociationMap = new HashMap<>();
+    @GuardedBy("mLock")
+    private final Map<Integer, Integer> mUserToMaxId = new HashMap<>();
+
+    @GuardedBy("mLocalListeners")
+    private final Set<OnChangeListener> mLocalListeners = new LinkedHashSet<>();
+    @GuardedBy("mRemoteListeners")
+    private final RemoteCallbackList<IOnAssociationsChangedListener> mRemoteListeners =
+            new RemoteCallbackList<>();
+
+    private final UserManager mUserManager;
+    private final AssociationDiskStore mDiskStore;
+
+    public AssociationStore(UserManager userManager, AssociationDiskStore diskStore) {
+        mUserManager = userManager;
+        mDiskStore = diskStore;
+        mExecutor = Executors.newSingleThreadExecutor();
+    }
+
+    /**
+     * Load all alive users' associations from disk to cache.
+     */
+    public void refreshCache() {
+        Binder.withCleanCallingIdentity(() -> {
+            List<Integer> userIds = new ArrayList<>();
+            for (UserInfo user : mUserManager.getAliveUsers()) {
+                userIds.add(user.id);
+            }
+
+            synchronized (mLock) {
+                mPersisted = false;
+
+                mIdToAssociationMap.clear();
+                mUserToMaxId.clear();
+
+                // The data is stored in DE directories, so we can read the data for all users now
+                // (which would not be possible if the data was stored to CE directories).
+                Map<Integer, Associations> userToAssociationsMap =
+                        mDiskStore.readAssociationsByUsers(userIds);
+                for (Map.Entry<Integer, Associations> entry : userToAssociationsMap.entrySet()) {
+                    for (AssociationInfo association : entry.getValue().getAssociations()) {
+                        mIdToAssociationMap.put(association.getId(), association);
+                    }
+                    mUserToMaxId.put(entry.getKey(), entry.getValue().getMaxId());
+                }
+
+                mPersisted = true;
+            }
+        });
+    }
+
+    /**
+     * Get the current max association id.
+     */
+    public int getMaxId(int userId) {
+        synchronized (mLock) {
+            return mUserToMaxId.getOrDefault(userId, 0);
+        }
+    }
+
+    /**
+     * Get the next available association id.
+     */
+    public int getNextId(int userId) {
+        synchronized (mLock) {
+            return getMaxId(userId) + 1;
+        }
+    }
 
     /**
      * Add an association.
      */
     public void addAssociation(@NonNull AssociationInfo association) {
-        Slog.i(TAG, "Adding new association=" + association);
-
-        // Validity check first.
-        checkNotRevoked(association);
+        Slog.i(TAG, "Adding new association=[" + association + "]...");
 
         final int id = association.getId();
+        final int userId = association.getUserId();
 
         synchronized (mLock) {
-            if (mIdMap.containsKey(id)) {
-                Slog.e(TAG, "Association with id " + id + " already exists.");
+            if (mIdToAssociationMap.containsKey(id)) {
+                Slog.e(TAG, "Association with id=[" + id + "] already exists.");
                 return;
             }
-            mIdMap.put(id, association);
 
-            final MacAddress address = association.getDeviceMacAddress();
-            if (address != null) {
-                mAddressMap.computeIfAbsent(address, it -> new HashSet<>()).add(id);
-            }
+            mIdToAssociationMap.put(id, association);
+            mUserToMaxId.put(userId, Math.max(mUserToMaxId.getOrDefault(userId, 0), id));
 
-            invalidateCacheForUserLocked(association.getUserId());
+            writeCacheToDisk(userId);
 
             Slog.i(TAG, "Done adding new association.");
         }
 
+        logCreateAssociation(association.getDeviceProfile());
+
         broadcastChange(CHANGE_TYPE_ADDED, association);
     }
 
@@ -154,18 +223,16 @@
      * Update an association.
      */
     public void updateAssociation(@NonNull AssociationInfo updated) {
-        Slog.i(TAG, "Updating new association=" + updated);
-        // Validity check first.
-        checkNotRevoked(updated);
+        Slog.i(TAG, "Updating new association=[" + updated + "]...");
 
         final int id = updated.getId();
-
         final AssociationInfo current;
         final boolean macAddressChanged;
+
         synchronized (mLock) {
-            current = mIdMap.get(id);
+            current = mIdToAssociationMap.get(id);
             if (current == null) {
-                Slog.w(TAG, "Can't update association. It does not exist.");
+                Slog.w(TAG, "Can't update association id=[" + id + "]. It does not exist.");
                 return;
             }
 
@@ -174,174 +241,238 @@
                 return;
             }
 
-            // Update the ID-to-Association map.
-            mIdMap.put(id, updated);
-            // Invalidate the corresponding user cache entry.
-            invalidateCacheForUserLocked(current.getUserId());
+            mIdToAssociationMap.put(id, updated);
 
-            // Update the MacAddress-to-List<Association> map if needed.
-            final MacAddress updatedAddress = updated.getDeviceMacAddress();
-            final MacAddress currentAddress = current.getDeviceMacAddress();
-            macAddressChanged = !Objects.equals(currentAddress, updatedAddress);
-            if (macAddressChanged) {
-                if (currentAddress != null) {
-                    mAddressMap.get(currentAddress).remove(id);
-                }
-                if (updatedAddress != null) {
-                    mAddressMap.computeIfAbsent(updatedAddress, it -> new HashSet<>()).add(id);
-                }
-            }
-            Slog.i(TAG, "Done updating association.");
+            writeCacheToDisk(updated.getUserId());
         }
 
+        Slog.i(TAG, "Done updating association.");
+
+        // Check if the MacAddress has changed.
+        final MacAddress updatedAddress = updated.getDeviceMacAddress();
+        final MacAddress currentAddress = current.getDeviceMacAddress();
+        macAddressChanged = !Objects.equals(currentAddress, updatedAddress);
+
         final int changeType = macAddressChanged ? CHANGE_TYPE_UPDATED_ADDRESS_CHANGED
                 : CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED;
+
         broadcastChange(changeType, updated);
     }
 
     /**
-     * Remove an association
+     * Remove an association.
      */
     public void removeAssociation(int id) {
-        Slog.i(TAG, "Removing association id=" + id);
+        Slog.i(TAG, "Removing association id=[" + id + "]...");
 
         final AssociationInfo association;
+
         synchronized (mLock) {
-            association = mIdMap.remove(id);
+            association = mIdToAssociationMap.remove(id);
 
             if (association == null) {
-                Slog.w(TAG, "Can't remove association. It does not exist.");
+                Slog.w(TAG, "Can't remove association id=[" + id + "]. It does not exist.");
                 return;
             }
 
-            final MacAddress macAddress = association.getDeviceMacAddress();
-            if (macAddress != null) {
-                mAddressMap.get(macAddress).remove(id);
-            }
-
-            invalidateCacheForUserLocked(association.getUserId());
+            writeCacheToDisk(association.getUserId());
 
             Slog.i(TAG, "Done removing association.");
         }
 
+        logRemoveAssociation(association.getDeviceProfile());
+
         broadcastChange(CHANGE_TYPE_REMOVED, association);
     }
 
+    private void writeCacheToDisk(@UserIdInt int userId) {
+        mExecutor.execute(() -> {
+            Associations associations = new Associations();
+            synchronized (mLock) {
+                associations.setMaxId(mUserToMaxId.getOrDefault(userId, 0));
+                associations.setAssociations(
+                        CollectionUtils.filter(mIdToAssociationMap.values().stream().toList(),
+                                a -> a.getUserId() == userId));
+            }
+            mDiskStore.writeAssociationsForUser(userId, associations);
+        });
+    }
+
     /**
-     * @return a "snapshot" of the current state of the existing associations.
+     * Get a copy of all associations including pending and revoked ones.
+     * Modifying the copy won't modify the actual associations.
+     *
+     * If a cache miss happens, read from disk.
      */
-    public @NonNull Collection<AssociationInfo> getAssociations() {
+    @NonNull
+    public List<AssociationInfo> getAssociations() {
         synchronized (mLock) {
-            // IMPORTANT: make and return a COPY of the mIdMap.values(), NOT a "direct" reference.
-            // The HashMap.values() returns a collection which is backed by the HashMap, so changes
-            // to the HashMap are reflected in this collection.
-            // For us this means that if mIdMap is modified while the iteration over mIdMap.values()
-            // is in progress it may lead to "undefined results" (according to the HashMap's
-            // documentation) or cause ConcurrentModificationExceptions in the iterator (according
-            // to the bugreports...).
-            return List.copyOf(mIdMap.values());
+            if (!mPersisted) {
+                refreshCache();
+            }
+            return List.copyOf(mIdToAssociationMap.values());
         }
     }
 
     /**
-     * Get associations for the user.
+     * Get a copy of active associations.
      */
-    public @NonNull List<AssociationInfo> getAssociationsForUser(@UserIdInt int userId) {
+    @NonNull
+    public List<AssociationInfo> getActiveAssociations() {
         synchronized (mLock) {
-            return getAssociationsForUserLocked(userId);
+            return CollectionUtils.filter(getAssociations(), AssociationInfo::isActive);
         }
     }
 
     /**
-     * Get associations for the package
+     * Get a copy of all associations by user.
      */
-    public @NonNull List<AssociationInfo> getAssociationsForPackage(
-            @UserIdInt int userId, @NonNull String packageName) {
-        final List<AssociationInfo> associationsForUser = getAssociationsForUser(userId);
-        final List<AssociationInfo> associationsForPackage =
-                CollectionUtils.filter(associationsForUser,
-                        it -> it.getPackageName().equals(packageName));
-        return Collections.unmodifiableList(associationsForPackage);
+    @NonNull
+    public List<AssociationInfo> getAssociationsByUser(@UserIdInt int userId) {
+        synchronized (mLock) {
+            return CollectionUtils.filter(getAssociations(), a -> a.getUserId() == userId);
+        }
     }
 
     /**
-     * Get associations by mac address for the package.
+     * Get a copy of active associations by user.
      */
-    public @Nullable AssociationInfo getAssociationsForPackageWithAddress(
+    @NonNull
+    public List<AssociationInfo> getActiveAssociationsByUser(@UserIdInt int userId) {
+        synchronized (mLock) {
+            return CollectionUtils.filter(getActiveAssociations(), a -> a.getUserId() == userId);
+        }
+    }
+
+    /**
+     * Get a copy of all associations by package.
+     */
+    @NonNull
+    public List<AssociationInfo> getAssociationsByPackage(@UserIdInt int userId,
+            @NonNull String packageName) {
+        synchronized (mLock) {
+            return CollectionUtils.filter(getAssociationsByUser(userId),
+                    a -> a.getPackageName().equals(packageName));
+        }
+    }
+
+    /**
+     * Get a copy of active associations by package.
+     */
+    @NonNull
+    public List<AssociationInfo> getActiveAssociationsByPackage(@UserIdInt int userId,
+            @NonNull String packageName) {
+        synchronized (mLock) {
+            return CollectionUtils.filter(getActiveAssociationsByUser(userId),
+                    a -> a.getPackageName().equals(packageName));
+        }
+    }
+
+    /**
+     * Get the first active association with the mac address.
+     */
+    @Nullable
+    public AssociationInfo getFirstAssociationByAddress(
             @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) {
-        final List<AssociationInfo> associations = getAssociationsByAddress(macAddress);
-        return CollectionUtils.find(associations,
-                it -> it.belongsToPackage(userId, packageName));
-    }
-
-    /**
-     * Get association by id.
-     */
-    public @Nullable AssociationInfo getAssociationById(int id) {
         synchronized (mLock) {
-            return mIdMap.get(id);
+            return CollectionUtils.find(getActiveAssociationsByPackage(userId, packageName),
+                    a -> a.getDeviceMacAddress() != null && a.getDeviceMacAddress()
+                            .equals(MacAddress.fromString(macAddress)));
         }
     }
 
     /**
-     * Get associations by mac address.
+     * Get the association by id.
+     */
+    @Nullable
+    public AssociationInfo getAssociationById(int id) {
+        synchronized (mLock) {
+            return mIdToAssociationMap.get(id);
+        }
+    }
+
+    /**
+     * Get a copy of active associations by mac address.
      */
     @NonNull
-    public List<AssociationInfo> getAssociationsByAddress(@NonNull String macAddress) {
-        final MacAddress address = MacAddress.fromString(macAddress);
-
+    public List<AssociationInfo> getActiveAssociationsByAddress(@NonNull String macAddress) {
         synchronized (mLock) {
-            final Set<Integer> ids = mAddressMap.get(address);
-            if (ids == null) return Collections.emptyList();
-
-            final List<AssociationInfo> associations = new ArrayList<>(ids.size());
-            for (Integer id : ids) {
-                associations.add(mIdMap.get(id));
-            }
-
-            return Collections.unmodifiableList(associations);
+            return CollectionUtils.filter(getActiveAssociations(),
+                    a -> a.getDeviceMacAddress() != null && a.getDeviceMacAddress()
+                            .equals(MacAddress.fromString(macAddress)));
         }
     }
 
-    @GuardedBy("mLock")
+    /**
+     * Get a copy of revoked associations.
+     */
     @NonNull
-    private List<AssociationInfo> getAssociationsForUserLocked(@UserIdInt int userId) {
-        final List<AssociationInfo> cached = mCachedPerUser.get(userId);
-        if (cached != null) {
-            return cached;
-        }
-
-        final List<AssociationInfo> associationsForUser = new ArrayList<>();
-        for (AssociationInfo association : mIdMap.values()) {
-            if (association.getUserId() == userId) {
-                associationsForUser.add(association);
-            }
-        }
-        final List<AssociationInfo> set = Collections.unmodifiableList(associationsForUser);
-        mCachedPerUser.set(userId, set);
-        return set;
-    }
-
-    @GuardedBy("mLock")
-    private void invalidateCacheForUserLocked(@UserIdInt int userId) {
-        mCachedPerUser.delete(userId);
-    }
-
-    /**
-     * Register a listener for association changes.
-     */
-    public void registerListener(@NonNull OnChangeListener listener) {
-        synchronized (mListeners) {
-            mListeners.add(listener);
+    public List<AssociationInfo> getRevokedAssociations() {
+        synchronized (mLock) {
+            return CollectionUtils.filter(getAssociations(), AssociationInfo::isRevoked);
         }
     }
 
     /**
-     * Unregister a listener previously registered for association changes.
+     * Get a copy of revoked associations for the package.
      */
-    public void unregisterListener(@NonNull OnChangeListener listener) {
-        synchronized (mListeners) {
-            mListeners.remove(listener);
+    @NonNull
+    public List<AssociationInfo> getRevokedAssociations(@UserIdInt int userId,
+            @NonNull String packageName) {
+        synchronized (mLock) {
+            return CollectionUtils.filter(getAssociations(),
+                    a -> packageName.equals(a.getPackageName()) && a.getUserId() == userId
+                            && a.isRevoked());
+        }
+    }
+
+    /**
+     * Get a copy of active associations.
+     */
+    @NonNull
+    public List<AssociationInfo> getPendingAssociations(@UserIdInt int userId,
+            @NonNull String packageName) {
+        synchronized (mLock) {
+            return CollectionUtils.filter(getAssociations(),
+                    a -> packageName.equals(a.getPackageName()) && a.getUserId() == userId
+                            && a.isPending());
+        }
+    }
+
+    /**
+     * Register a local listener for association changes.
+     */
+    public void registerLocalListener(@NonNull OnChangeListener listener) {
+        synchronized (mLocalListeners) {
+            mLocalListeners.add(listener);
+        }
+    }
+
+    /**
+     * Unregister a local listener previously registered for association changes.
+     */
+    public void unregisterLocalListener(@NonNull OnChangeListener listener) {
+        synchronized (mLocalListeners) {
+            mLocalListeners.remove(listener);
+        }
+    }
+
+    /**
+     * Register a remote listener for association changes.
+     */
+    public void registerRemoteListener(@NonNull IOnAssociationsChangedListener listener,
+            int userId) {
+        synchronized (mRemoteListeners) {
+            mRemoteListeners.register(listener, userId);
+        }
+    }
+
+    /**
+     * Unregister a remote listener previously registered for association changes.
+     */
+    public void unregisterRemoteListener(@NonNull IOnAssociationsChangedListener listener) {
+        synchronized (mRemoteListeners) {
+            mRemoteListeners.unregister(listener);
         }
     }
 
@@ -350,52 +481,39 @@
      */
     public void dump(@NonNull PrintWriter out) {
         out.append("Companion Device Associations: ");
-        if (getAssociations().isEmpty()) {
+        if (getActiveAssociations().isEmpty()) {
             out.append("<empty>\n");
         } else {
             out.append("\n");
-            for (AssociationInfo a : getAssociations()) {
+            for (AssociationInfo a : getActiveAssociations()) {
                 out.append("  ").append(a.toString()).append('\n');
             }
         }
     }
 
     private void broadcastChange(@ChangeType int changeType, AssociationInfo association) {
-        synchronized (mListeners) {
-            for (OnChangeListener listener : mListeners) {
+        synchronized (mLocalListeners) {
+            for (OnChangeListener listener : mLocalListeners) {
                 listener.onAssociationChanged(changeType, association);
             }
         }
-    }
-
-    /**
-     * Set associations to cache. It will clear the existing cache.
-     */
-    public void setAssociationsToCache(Collection<AssociationInfo> associations) {
-        // Validity check first.
-        associations.forEach(AssociationStore::checkNotRevoked);
-
-        synchronized (mLock) {
-            mIdMap.clear();
-            mAddressMap.clear();
-            mCachedPerUser.clear();
-
-            for (AssociationInfo association : associations) {
-                final int id = association.getId();
-                mIdMap.put(id, association);
-
-                final MacAddress address = association.getDeviceMacAddress();
-                if (address != null) {
-                    mAddressMap.computeIfAbsent(address, it -> new HashSet<>()).add(id);
-                }
+        synchronized (mRemoteListeners) {
+            final int userId = association.getUserId();
+            final List<AssociationInfo> updatedAssociations = getActiveAssociationsByUser(userId);
+            // Notify listeners if ADDED, REMOVED or UPDATED_ADDRESS_CHANGED.
+            // Do NOT notify when UPDATED_ADDRESS_UNCHANGED, which means a minor tweak in
+            // association's configs, which "listeners" won't (and shouldn't) be able to see.
+            if (changeType != CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED) {
+                mRemoteListeners.broadcast((listener, callbackUserId) -> {
+                    int listenerUserId = (int) callbackUserId;
+                    if (listenerUserId == userId || listenerUserId == UserHandle.USER_ALL) {
+                        try {
+                            listener.onAssociationsChanged(updatedAssociations);
+                        } catch (RemoteException ignored) {
+                        }
+                    }
+                });
             }
         }
     }
-
-    private static void checkNotRevoked(@NonNull AssociationInfo association) {
-        if (association.isRevoked()) {
-            throw new IllegalArgumentException(
-                    "Revoked (removed) associations MUST NOT appear in the AssociationStore");
-        }
-    }
 }
diff --git a/services/companion/java/com/android/server/companion/association/Associations.java b/services/companion/java/com/android/server/companion/association/Associations.java
new file mode 100644
index 0000000..7da3699
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/association/Associations.java
@@ -0,0 +1,68 @@
+/*
+ * 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.companion.association;
+
+import android.companion.AssociationInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents associations per user. Should be only used by Association stores.
+ */
+public class Associations {
+
+    private int mVersion = 0;
+
+    private List<AssociationInfo> mAssociations = new ArrayList<>();
+
+    private int mMaxId = 0;
+
+    public Associations() {
+    }
+
+    public void setVersion(int version) {
+        mVersion = version;
+    }
+
+    /**
+     * Add an association.
+     */
+    public void addAssociation(AssociationInfo association) {
+        mAssociations.add(association);
+    }
+
+    public void setMaxId(int maxId) {
+        mMaxId = maxId;
+    }
+
+    public void setAssociations(List<AssociationInfo> associations) {
+        mAssociations = List.copyOf(associations);
+    }
+
+    public int getVersion() {
+        return mVersion;
+    }
+
+    public int getMaxId() {
+        return mMaxId;
+    }
+
+    public List<AssociationInfo> getAssociations() {
+        return mAssociations;
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
new file mode 100644
index 0000000..ec897791
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
@@ -0,0 +1,218 @@
+/*
+ * 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.companion.association;
+
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
+
+import static com.android.internal.util.CollectionUtils.any;
+import static com.android.server.companion.utils.RolesUtils.removeRoleHolderForAssociation;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.companion.AssociationInfo;
+import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+import android.os.Binder;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import com.android.server.companion.CompanionApplicationController;
+import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
+import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
+import com.android.server.companion.transport.CompanionTransportManager;
+
+/**
+ * A class response for Association removal.
+ */
+@SuppressLint("LongLogTag")
+public class DisassociationProcessor {
+
+    private static final String TAG = "CDM_DisassociationProcessor";
+    @NonNull
+    private final Context mContext;
+    @NonNull
+    private final AssociationStore mAssociationStore;
+    @NonNull
+    private final PackageManagerInternal mPackageManagerInternal;
+    @NonNull
+    private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
+    @NonNull
+    private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
+    @NonNull
+    private final CompanionApplicationController mCompanionAppController;
+    @NonNull
+    private final CompanionTransportManager mTransportManager;
+    private final OnPackageVisibilityChangeListener mOnPackageVisibilityChangeListener;
+    private final ActivityManager mActivityManager;
+
+    public DisassociationProcessor(@NonNull Context context,
+            @NonNull ActivityManager activityManager,
+            @NonNull AssociationStore associationStore,
+            @NonNull PackageManagerInternal packageManager,
+            @NonNull CompanionDevicePresenceMonitor devicePresenceMonitor,
+            @NonNull CompanionApplicationController applicationController,
+            @NonNull SystemDataTransferRequestStore systemDataTransferRequestStore,
+            @NonNull CompanionTransportManager companionTransportManager) {
+        mContext = context;
+        mActivityManager = activityManager;
+        mAssociationStore = associationStore;
+        mPackageManagerInternal = packageManager;
+        mOnPackageVisibilityChangeListener =
+                new OnPackageVisibilityChangeListener();
+        mDevicePresenceMonitor = devicePresenceMonitor;
+        mCompanionAppController = applicationController;
+        mSystemDataTransferRequestStore = systemDataTransferRequestStore;
+        mTransportManager = companionTransportManager;
+    }
+
+    /**
+     * Disassociate an association by id.
+     */
+    // TODO: also revoke notification access
+    public void disassociate(int id) {
+        Slog.i(TAG, "Disassociating id=[" + id + "]...");
+
+        final AssociationInfo association = mAssociationStore.getAssociationById(id);
+        if (association == null) {
+            Slog.e(TAG, "Can't disassociate id=[" + id + "]. It doesn't exist.");
+            return;
+        }
+
+        final int userId = association.getUserId();
+        final String packageName = association.getPackageName();
+        final String deviceProfile = association.getDeviceProfile();
+
+        final boolean isRoleInUseByOtherAssociations = deviceProfile != null
+                && any(mAssociationStore.getActiveAssociationsByPackage(userId, packageName),
+                    it -> deviceProfile.equals(it.getDeviceProfile()) && id != it.getId());
+
+        final int packageProcessImportance = getPackageProcessImportance(userId, packageName);
+        if (packageProcessImportance <= IMPORTANCE_VISIBLE && deviceProfile != null
+                && !isRoleInUseByOtherAssociations) {
+            // Need to remove the app from the list of role holders, but the process is visible
+            // to the user at the moment, so we'll need to do it later.
+            Slog.i(TAG, "Cannot disassociate id=[" + id + "] now - process is visible. "
+                    + "Start listening to package importance...");
+
+            AssociationInfo revokedAssociation = (new AssociationInfo.Builder(
+                    association)).setRevoked(true).build();
+            mAssociationStore.updateAssociation(revokedAssociation);
+            startListening();
+            return;
+        }
+
+        // Association cleanup.
+        mAssociationStore.removeAssociation(association.getId());
+        mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, id);
+
+        // Detach transport if exists
+        mTransportManager.detachSystemDataTransport(packageName, userId, id);
+
+        // If role is not in use by other associations, revoke the role.
+        // Do not need to remove the system role since it was pre-granted by the system.
+        if (!isRoleInUseByOtherAssociations && deviceProfile != null && !deviceProfile.equals(
+                DEVICE_PROFILE_AUTOMOTIVE_PROJECTION)) {
+            removeRoleHolderForAssociation(mContext, association.getUserId(),
+                    association.getPackageName(), association.getDeviceProfile());
+        }
+
+        // Unbind the app if needed.
+        final boolean wasPresent = mDevicePresenceMonitor.isDevicePresent(id);
+        if (!wasPresent || !association.isNotifyOnDeviceNearby()) {
+            return;
+        }
+        final boolean shouldStayBound = any(
+                mAssociationStore.getActiveAssociationsByPackage(userId, packageName),
+                it -> it.isNotifyOnDeviceNearby()
+                        && mDevicePresenceMonitor.isDevicePresent(it.getId()));
+        if (!shouldStayBound) {
+            mCompanionAppController.unbindCompanionApplication(userId, packageName);
+        }
+    }
+
+    @SuppressLint("MissingPermission")
+    private int getPackageProcessImportance(@UserIdInt int userId, @NonNull String packageName) {
+        return Binder.withCleanCallingIdentity(() -> {
+            final int uid =
+                    mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId);
+            return mActivityManager.getUidImportance(uid);
+        });
+    }
+
+    private void startListening() {
+        Slog.i(TAG, "Start listening to uid importance changes...");
+        try {
+            Binder.withCleanCallingIdentity(
+                    () -> mActivityManager.addOnUidImportanceListener(
+                            mOnPackageVisibilityChangeListener,
+                            ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE));
+        }  catch (IllegalArgumentException e) {
+            Slog.e(TAG, "Failed to start listening to uid importance changes.");
+        }
+    }
+
+    private void stopListening() {
+        Slog.i(TAG, "Stop listening to uid importance changes.");
+        try {
+            Binder.withCleanCallingIdentity(() -> mActivityManager.removeOnUidImportanceListener(
+                    mOnPackageVisibilityChangeListener));
+        } catch (IllegalArgumentException e) {
+            Slog.e(TAG, "Failed to stop listening to uid importance changes.");
+        }
+    }
+
+    /**
+     * An OnUidImportanceListener class which watches the importance of the packages.
+     * In this class, we ONLY interested in the importance of the running process is greater than
+     * {@link ActivityManager.RunningAppProcessInfo#IMPORTANCE_VISIBLE}.
+     *
+     * Lastly remove the role holder for the revoked associations for the same packages.
+     *
+     * @see #disassociate(int)
+     */
+    private class OnPackageVisibilityChangeListener implements
+            ActivityManager.OnUidImportanceListener {
+
+        @Override
+        public void onUidImportance(int uid, int importance) {
+            if (importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE) {
+                // The lower the importance value the more "important" the process is.
+                // We are only interested when the process ceases to be visible.
+                return;
+            }
+
+            final String packageName = mPackageManagerInternal.getNameForUid(uid);
+            if (packageName == null) {
+                // Not interested in this uid.
+                return;
+            }
+
+            int userId = UserHandle.getUserId(uid);
+            for (AssociationInfo association : mAssociationStore.getRevokedAssociations(userId,
+                    packageName)) {
+                disassociate(association.getId());
+            }
+
+            if (mAssociationStore.getRevokedAssociations().isEmpty()) {
+                stopListening();
+            }
+        }
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java b/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java
index 894c49a..f287315 100644
--- a/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java
+++ b/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java
@@ -33,7 +33,7 @@
  * A Job Service responsible for clean up idle self-managed associations.
  *
  * The job will be executed only if the device is charging and in idle mode due to the application
- * will be killed if association/role are revoked. See {@link AssociationRevokeProcessor}
+ * will be killed if association/role are revoked. See {@link DisassociationProcessor}
  */
 public class InactiveAssociationsRemovalService extends JobService {
 
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index a08e0da..c5ca0bf 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -186,18 +186,14 @@
         intent.putExtras(extras);
 
         // Create a PendingIntent
-        final long token = Binder.clearCallingIdentity();
-        try {
-            return PendingIntent.getActivityAsUser(mContext, /*requestCode */ associationId, intent,
-                    FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE,
-                    ActivityOptions.makeBasic()
-                            .setPendingIntentCreatorBackgroundActivityStartMode(
-                                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
-                            .toBundle(),
-                    UserHandle.CURRENT);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
+        return Binder.withCleanCallingIdentity(() ->
+                PendingIntent.getActivityAsUser(mContext, /*requestCode */ associationId,
+                        intent, FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE,
+                        ActivityOptions.makeBasic()
+                                .setPendingIntentCreatorBackgroundActivityStartMode(
+                                        ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+                                .toBundle(),
+                        UserHandle.CURRENT));
     }
 
     /**
@@ -228,8 +224,7 @@
         }
 
         // Start permission sync
-        final long callingIdentityToken = Binder.clearCallingIdentity();
-        try {
+        Binder.withCleanCallingIdentity(() -> {
             // TODO: refactor to work with streams of data
             mPermissionControllerManager.getRuntimePermissionBackup(UserHandle.of(userId),
                     mExecutor, backup -> {
@@ -237,39 +232,31 @@
                                 .requestPermissionRestore(associationId, backup);
                         translateFutureToCallback(future, callback);
                     });
-        } finally {
-            Binder.restoreCallingIdentity(callingIdentityToken);
-        }
+        });
     }
 
     /**
      * Enable perm sync for the association
      */
     public void enablePermissionsSync(int associationId) {
-        final long callingIdentityToken = Binder.clearCallingIdentity();
-        try {
+        Binder.withCleanCallingIdentity(() -> {
             int userId = mAssociationStore.getAssociationById(associationId).getUserId();
             PermissionSyncRequest request = new PermissionSyncRequest(associationId);
             request.setUserConsented(true);
             mSystemDataTransferRequestStore.writeRequest(userId, request);
-        } finally {
-            Binder.restoreCallingIdentity(callingIdentityToken);
-        }
+        });
     }
 
     /**
      * Disable perm sync for the association
      */
     public void disablePermissionsSync(int associationId) {
-        final long callingIdentityToken = Binder.clearCallingIdentity();
-        try {
+        Binder.withCleanCallingIdentity(() -> {
             int userId = mAssociationStore.getAssociationById(associationId).getUserId();
             PermissionSyncRequest request = new PermissionSyncRequest(associationId);
             request.setUserConsented(false);
             mSystemDataTransferRequestStore.writeRequest(userId, request);
-        } finally {
-            Binder.restoreCallingIdentity(callingIdentityToken);
-        }
+        });
     }
 
     /**
@@ -277,8 +264,7 @@
      */
     @Nullable
     public PermissionSyncRequest getPermissionSyncRequest(int associationId) {
-        final long callingIdentityToken = Binder.clearCallingIdentity();
-        try {
+        return Binder.withCleanCallingIdentity(() -> {
             int userId = mAssociationStore.getAssociationById(associationId).getUserId();
             List<SystemDataTransferRequest> requests =
                     mSystemDataTransferRequestStore.readRequestsByAssociationId(userId,
@@ -289,22 +275,17 @@
                 }
             }
             return null;
-        } finally {
-            Binder.restoreCallingIdentity(callingIdentityToken);
-        }
+        });
     }
 
     /**
      * Remove perm sync request for the association.
      */
     public void removePermissionSyncRequest(int associationId) {
-        final long callingIdentityToken = Binder.clearCallingIdentity();
-        try {
+        Binder.withCleanCallingIdentity(() -> {
             int userId = mAssociationStore.getAssociationById(associationId).getUserId();
             mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, associationId);
-        } finally {
-            Binder.restoreCallingIdentity(callingIdentityToken);
-        }
+        });
     }
 
     private void onReceivePermissionRestore(byte[] message) {
@@ -318,14 +299,12 @@
         Slog.i(LOG_TAG, "Applying permissions.");
         // Start applying permissions
         UserHandle user = mContext.getUser();
-        final long callingIdentityToken = Binder.clearCallingIdentity();
-        try {
+
+        Binder.withCleanCallingIdentity(() -> {
             // TODO: refactor to work with streams of data
             mPermissionControllerManager.stageAndApplyRuntimePermissionsBackup(
                     message, user);
-        } finally {
-            Binder.restoreCallingIdentity(callingIdentityToken);
-        }
+        });
     }
 
     private static void translateFutureToCallback(@NonNull Future<?> future,
diff --git a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
index 99466a9..c89ce11 100644
--- a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
+++ b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
@@ -106,7 +106,7 @@
         checkBleState();
         registerBluetoothStateBroadcastReceiver(context);
 
-        mAssociationStore.registerListener(this);
+        mAssociationStore.registerLocalListener(this);
     }
 
     @MainThread
@@ -183,7 +183,7 @@
 
         // Collect MAC addresses from all associations.
         final Set<String> macAddresses = new HashSet<>();
-        for (AssociationInfo association : mAssociationStore.getAssociations()) {
+        for (AssociationInfo association : mAssociationStore.getActiveAssociations()) {
             if (!association.isNotifyOnDeviceNearby()) continue;
 
             // Beware that BT stack does not consider low-case MAC addresses valid, while
@@ -255,7 +255,7 @@
         if (DEBUG) Log.i(TAG, "notifyDevice_Found()" + btDeviceToString(device));
 
         final List<AssociationInfo> associations =
-                mAssociationStore.getAssociationsByAddress(device.getAddress());
+                mAssociationStore.getActiveAssociationsByAddress(device.getAddress());
         if (DEBUG) Log.d(TAG, "  > associations=" + Arrays.toString(associations.toArray()));
 
         for (AssociationInfo association : associations) {
@@ -268,7 +268,7 @@
         if (DEBUG) Log.i(TAG, "notifyDevice_Lost()" + btDeviceToString(device));
 
         final List<AssociationInfo> associations =
-                mAssociationStore.getAssociationsByAddress(device.getAddress());
+                mAssociationStore.getActiveAssociationsByAddress(device.getAddress());
         if (DEBUG) Log.d(TAG, "  > associations=" + Arrays.toString(associations.toArray()));
 
         for (AssociationInfo association : associations) {
@@ -319,7 +319,7 @@
                 Log.v(TAG, "  > scanResult=" + result);
 
                 final List<AssociationInfo> associations =
-                        mAssociationStore.getAssociationsByAddress(device.getAddress());
+                        mAssociationStore.getActiveAssociationsByAddress(device.getAddress());
                 Log.v(TAG, "  > associations=" + Arrays.toString(associations.toArray()));
             }
 
diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
index 4da3f9b..cb363a7 100644
--- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
+++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
@@ -93,7 +93,7 @@
 
         btAdapter.registerBluetoothConnectionCallback(
                 new HandlerExecutor(Handler.getMain()), /* callback */this);
-        mAssociationStore.registerListener(this);
+        mAssociationStore.registerLocalListener(this);
     }
 
     /**
@@ -168,7 +168,7 @@
     private void onDeviceConnectivityChanged(@NonNull BluetoothDevice device, boolean connected) {
         int userId = UserHandle.myUserId();
         final List<AssociationInfo> associations =
-                mAssociationStore.getAssociationsByAddress(device.getAddress());
+                mAssociationStore.getActiveAssociationsByAddress(device.getAddress());
         final List<ObservableUuid> observableUuids =
                 mObservableUuidStore.getObservableUuidsForUser(userId);
         final ParcelUuid[] bluetoothDeviceUuids = device.getUuids();
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
index 37bbb93..7a1a83f 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
+++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
@@ -145,7 +145,7 @@
             Log.w(TAG, "BluetoothAdapter is NOT available.");
         }
 
-        mAssociationStore.registerListener(this);
+        mAssociationStore.registerLocalListener(this);
     }
 
     /**
@@ -481,7 +481,7 @@
      * BT connected and BLE presence and are not pending to report BLE lost.
      */
     private boolean canStopBleScan() {
-        for (AssociationInfo ai : mAssociationStore.getAssociations()) {
+        for (AssociationInfo ai : mAssociationStore.getActiveAssociations()) {
             int id = ai.getId();
             synchronized (mBtDisconnectedDevices) {
                 if (ai.isNotifyOnDeviceNearby() && !(isBtConnected(id)
diff --git a/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java b/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java
index ee8b106..db15da29 100644
--- a/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java
+++ b/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java
@@ -90,6 +90,8 @@
      * Remove the observable uuid.
      */
     public void removeObservableUuid(@UserIdInt int userId, ParcelUuid uuid, String packageName) {
+        Slog.i(TAG, "Removing uuid=[" + uuid.getUuid() + "] from store...");
+
         List<ObservableUuid> cachedObservableUuids;
 
         synchronized (mLock) {
@@ -108,7 +110,7 @@
      * Write the observable uuid.
      */
     public void writeObservableUuid(@UserIdInt int userId, ObservableUuid uuid) {
-        Slog.i(TAG, "Writing uuid=" + uuid.getUuid() + " to store.");
+        Slog.i(TAG, "Writing uuid=[" + uuid.getUuid() + "] to store...");
 
         List<ObservableUuid> cachedObservableUuids;
         synchronized (mLock) {
diff --git a/services/companion/java/com/android/server/companion/utils/DataStoreUtils.java b/services/companion/java/com/android/server/companion/utils/DataStoreUtils.java
index c75b1a5..369a925 100644
--- a/services/companion/java/com/android/server/companion/utils/DataStoreUtils.java
+++ b/services/companion/java/com/android/server/companion/utils/DataStoreUtils.java
@@ -64,8 +64,8 @@
      * IMPORTANT: the method will ALWAYS return the same {@link AtomicFile} object, which makes it
      * possible to synchronize reads and writes to the file using the returned object.
      *
-     * @param userId              the userId to retrieve the storage file
-     * @param fileName         the storage file name
+     * @param userId the userId to retrieve the storage file
+     * @param fileName the storage file name
      * @return an AtomicFile for the user
      */
     @NonNull
diff --git a/services/companion/java/com/android/server/companion/utils/RolesUtils.java b/services/companion/java/com/android/server/companion/utils/RolesUtils.java
index f798e21..dd12e04 100644
--- a/services/companion/java/com/android/server/companion/utils/RolesUtils.java
+++ b/services/companion/java/com/android/server/companion/utils/RolesUtils.java
@@ -93,8 +93,8 @@
 
         Slog.i(TAG, "Removing CDM role=" + deviceProfile
                 + " for userId=" + userId + ", packageName=" + packageName);
-        final long identity = Binder.clearCallingIdentity();
-        try {
+
+        Binder.withCleanCallingIdentity(() ->
             roleManager.removeRoleHolderAsUser(deviceProfile, packageName,
                     MANAGE_HOLDERS_FLAG_DONT_KILL_APP, userHandle, context.getMainExecutor(),
                     success -> {
@@ -103,11 +103,9 @@
                                     + packageName + " from the list of " + deviceProfile
                                     + " holders.");
                         }
-                    });
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
+                    })
+        );
     }
 
-    private RolesUtils() {};
+    private RolesUtils() {}
 }
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 5e6ff55..447dfd9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -9011,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/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index 3e633cc..ddf1d5f 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -211,11 +211,17 @@
             if (!mEnabled) {
                 return;
             }
-            if (!mInProgRecords.containsKey(id)) {
+            int index = mInProgRecords.indexOfKey(id);
+            if (index < 0) {
                 return;
             }
-            mInProgRecords.get(id).setStartupState(ApplicationStartInfo.STARTUP_STATE_ERROR);
-            mInProgRecords.remove(id);
+            ApplicationStartInfo info = mInProgRecords.valueAt(index);
+            if (info == null) {
+                mInProgRecords.removeAt(index);
+                return;
+            }
+            info.setStartupState(ApplicationStartInfo.STARTUP_STATE_ERROR);
+            mInProgRecords.removeAt(index);
         }
     }
 
@@ -224,16 +230,24 @@
             if (!mEnabled) {
                 return;
             }
-            if (!mInProgRecords.containsKey(id)) {
+            int index = mInProgRecords.indexOfKey(id);
+            if (index < 0) {
                 return;
             }
-            if (app != null) {
-                ApplicationStartInfo info = mInProgRecords.get(id);
-                info.setStartType((int) temperature);
-                addBaseFieldsFromProcessRecord(info, app);
-                mInProgRecords.put(id, addStartInfoLocked(info));
+            ApplicationStartInfo info = mInProgRecords.valueAt(index);
+            if (info == null || app == null) {
+                mInProgRecords.removeAt(index);
+                return;
+            }
+            info.setStartType((int) temperature);
+            addBaseFieldsFromProcessRecord(info, app);
+            ApplicationStartInfo newInfo = addStartInfoLocked(info);
+            if (newInfo == null) {
+                // newInfo can be null if records are added before load from storage is
+                // complete. In this case the newly added record will be lost.
+                mInProgRecords.removeAt(index);
             } else {
-                mInProgRecords.remove(id);
+                mInProgRecords.setValueAt(index, newInfo);
             }
         }
     }
@@ -243,12 +257,17 @@
             if (!mEnabled) {
                 return;
             }
-            if (!mInProgRecords.containsKey(id)) {
+            int index = mInProgRecords.indexOfKey(id);
+            if (index < 0) {
                 return;
             }
-            ApplicationStartInfo info = mInProgRecords.get(id);
+            ApplicationStartInfo info = mInProgRecords.valueAt(index);
+            if (info == null) {
+                mInProgRecords.removeAt(index);
+                return;
+            }
             info.setStartupState(ApplicationStartInfo.STARTUP_STATE_ERROR);
-            mInProgRecords.remove(id);
+            mInProgRecords.removeAt(index);
         }
     }
 
@@ -258,10 +277,15 @@
             if (!mEnabled) {
                 return;
             }
-            if (!mInProgRecords.containsKey(id)) {
+            int index = mInProgRecords.indexOfKey(id);
+            if (index < 0) {
                 return;
             }
-            ApplicationStartInfo info = mInProgRecords.get(id);
+            ApplicationStartInfo info = mInProgRecords.valueAt(index);
+            if (info == null) {
+                mInProgRecords.removeAt(index);
+                return;
+            }
             info.setStartupState(ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN);
             info.setLaunchMode(launchMode);
             checkCompletenessAndCallback(info);
@@ -273,13 +297,18 @@
             if (!mEnabled) {
                 return;
             }
-            if (!mInProgRecords.containsKey(id)) {
+            int index = mInProgRecords.indexOfKey(id);
+            if (index < 0) {
                 return;
             }
-            ApplicationStartInfo info = mInProgRecords.get(id);
+            ApplicationStartInfo info = mInProgRecords.valueAt(index);
+            if (info == null) {
+                mInProgRecords.removeAt(index);
+                return;
+            }
             info.addStartupTimestamp(ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN,
                     timestampNanos);
-            mInProgRecords.remove(id);
+            mInProgRecords.removeAt(index);
         }
     }
 
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index 3d695bc..5cb8b95 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -674,14 +674,16 @@
         return mScheduleServiceTimeoutPending;
     }
 
-    @GuardedBy("mService")
     void onProcessUnfrozen() {
-        scheduleServiceTimeoutIfNeededLocked();
+        synchronized (mService) {
+            scheduleServiceTimeoutIfNeededLocked();
+        }
     }
 
-    @GuardedBy("mService")
     void onProcessFrozenCancelled() {
-        scheduleServiceTimeoutIfNeededLocked();
+        synchronized (mService) {
+            scheduleServiceTimeoutIfNeededLocked();
+        }
     }
 
     @GuardedBy("mService")
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index b1823b4..e955b00 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -60,10 +60,3 @@
         purpose: PURPOSE_BUGFIX
     }
 }
-
-flag {
-    namespace: "backstage_power"
-    name: "defer_outgoing_bcasts"
-    description: "Defer outgoing broadcasts from processes in freezable state"
-    bug: "327496592"
-}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index d4f04b5..e8c05c6 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -7124,7 +7124,8 @@
 
         switch (mPlatformType) {
         case AudioSystem.PLATFORM_VOICE:
-            if (isInCommunication()) {
+            if (isInCommunication()
+                    || mAudioSystem.isStreamActive(AudioManager.STREAM_VOICE_CALL, 0)) {
                 if (mDeviceBroker.isBluetoothScoActive()) {
                     // Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO...");
                     return AudioSystem.STREAM_BLUETOOTH_SCO;
diff --git a/services/core/java/com/android/server/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/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/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 177c345..e2ae3de 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -76,7 +76,6 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Optional;
@@ -397,11 +396,10 @@
     @NonNull
     private DeviceStateInfo getDeviceStateInfoLocked() {
         final List<DeviceState> supportedStates = getSupportedStatesLocked();
-        final DeviceState baseState = mBaseState.orElse(null);
-        final DeviceState currentState = mCommittedState.orElse(null);
+        final DeviceState baseState = mBaseState.orElse(INVALID_DEVICE_STATE);
+        final DeviceState currentState = mCommittedState.orElse(INVALID_DEVICE_STATE);
 
-        return new DeviceStateInfo(supportedStates,
-                baseState != null ? baseState : INVALID_DEVICE_STATE,
+        return new DeviceStateInfo(supportedStates, baseState,
                 createMergedDeviceState(currentState, baseState));
     }
 
@@ -412,7 +410,7 @@
      */
     private DeviceState createMergedDeviceState(@Nullable DeviceState committedState,
             @Nullable DeviceState baseState) {
-        if (committedState == null) {
+        if (committedState.equals(INVALID_DEVICE_STATE)) {
             return INVALID_DEVICE_STATE;
         }
 
@@ -420,8 +418,7 @@
                 committedState.getConfiguration().getSystemProperties();
 
         Set<@DeviceState.DeviceStateProperties Integer> physicalProperties =
-                baseState != null ? baseState.getConfiguration().getPhysicalProperties()
-                        : Collections.emptySet();
+                baseState.getConfiguration().getPhysicalProperties();
 
         DeviceState.Configuration deviceStateConfiguration = new DeviceState.Configuration.Builder(
                 committedState.getIdentifier(), committedState.getName())
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 a7748f4..0ebb2a3 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -158,8 +158,6 @@
     @Nullable
     public Point getDisplaySurfaceDefaultSizeLocked() {
         DisplayDeviceInfo displayDeviceInfo = getDisplayDeviceInfoLocked();
-        final boolean isRotated = mCurrentOrientation == ROTATION_90
-                || mCurrentOrientation == ROTATION_270;
         var width = displayDeviceInfo.width;
         var height = displayDeviceInfo.height;
         if (mIsAnisotropyCorrectionEnabled && displayDeviceInfo.yDpi > 0
@@ -170,7 +168,7 @@
                 width = (int) (width * displayDeviceInfo.yDpi / displayDeviceInfo.xDpi  + 0.5);
             }
         }
-        return isRotated ? new Point(height, width) : new Point(width, height);
+        return isRotatedLocked() ? new Point(height, width) : new Point(width, height);
     }
 
     /**
@@ -394,8 +392,7 @@
             viewport.physicalFrame.setEmpty();
         }
 
-        boolean isRotated = (mCurrentOrientation == Surface.ROTATION_90
-                || mCurrentOrientation == ROTATION_270);
+        final boolean isRotated = isRotatedLocked();
         DisplayDeviceInfo info = getDisplayDeviceInfoLocked();
         viewport.deviceWidth = isRotated ? info.height : info.width;
         viewport.deviceHeight = isRotated ? info.width : info.height;
@@ -425,6 +422,13 @@
         pw.println("mCurrentSurface=" + mCurrentSurface);
     }
 
+    /**
+     * @return whether the orientation is {@link ROTATION_90} or {@link ROTATION_270}.
+     */
+    boolean isRotatedLocked() {
+        return mCurrentOrientation == ROTATION_90 || mCurrentOrientation == ROTATION_270;
+    }
+
     private DisplayDeviceConfig loadDisplayDeviceConfig() {
         return DisplayDeviceConfig.create(mContext, /* useConfigXml= */ false,
                 mDisplayAdapter.getFeatureFlags());
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 9b2dcc5..4116669 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -57,6 +57,9 @@
 import com.android.server.display.config.HbmTiming;
 import com.android.server.display.config.HdrBrightnessData;
 import com.android.server.display.config.HighBrightnessMode;
+import com.android.server.display.config.IdleScreenRefreshRateTimeout;
+import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint;
+import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholds;
 import com.android.server.display.config.IntegerArray;
 import com.android.server.display.config.LuxThrottling;
 import com.android.server.display.config.NitsMap;
@@ -553,6 +556,18 @@
  *         <minorVersion>0</minorVersion>
  *     </usiVersion>
  *     <screenBrightnessCapForWearBedtimeMode>0.1</screenBrightnessCapForWearBedtimeMode>
+ *     <idleScreenRefreshRateTimeout>
+ *          <luxThresholds>
+ *              <point>
+ *                  <lux>6</lux>
+ *                  <timeout>1000</timeout>
+ *              </point>
+ *              <point>
+ *                  <lux>10</lux>
+ *                  <timeout>800</timeout>
+ *              </point>
+ *          </luxThresholds>
+ *     </idleScreenRefreshRateTimeout>
  *    </displayConfiguration>
  *  }
  *  </pre>
@@ -843,6 +858,14 @@
     private final Map<BrightnessLimitMapType, Map<Float, Float>>
             mLuxThrottlingData = new HashMap<>();
 
+    /**
+     * The idle screen timeout configuration for switching to lower refresh rate
+     */
+    @NonNull
+    private List<IdleScreenRefreshRateTimeoutLuxThresholdPoint>
+            mIdleScreenRefreshRateTimeoutLuxThresholds = new ArrayList<>();
+
+
     @Nullable
     private HostUsiVersion mHostUsiVersion;
 
@@ -1999,6 +2022,7 @@
                 loadUsiVersion(config);
                 mHdrBrightnessData = HdrBrightnessData.loadConfig(config);
                 loadBrightnessCapForWearBedtimeMode(config);
+                loadIdleScreenRefreshRateTimeoutConfigs(config);
             } else {
                 Slog.w(TAG, "DisplayDeviceConfig file is null");
             }
@@ -2024,6 +2048,7 @@
         loadAutoBrightnessAvailableFromConfigXml();
         loadRefreshRateSetting(null);
         loadBrightnessCapForWearBedtimeModeFromConfigXml();
+        loadIdleScreenRefreshRateTimeoutConfigs(null);
         mLoadedFrom = "<config.xml>";
     }
 
@@ -3326,6 +3351,47 @@
         }
     }
 
+    private void loadIdleScreenRefreshRateTimeoutConfigs(@Nullable DisplayConfiguration config) {
+        if (mFlags.isIdleScreenRefreshRateTimeoutEnabled()
+                && config != null && config.getIdleScreenRefreshRateTimeout() != null) {
+            validateIdleScreenRefreshRateTimeoutConfig(
+                    config.getIdleScreenRefreshRateTimeout());
+            mIdleScreenRefreshRateTimeoutLuxThresholds = config
+                    .getIdleScreenRefreshRateTimeout().getLuxThresholds().getPoint();
+        }
+    }
+
+    private void validateIdleScreenRefreshRateTimeoutConfig(
+            IdleScreenRefreshRateTimeout idleScreenRefreshRateTimeoutConfig) {
+        IdleScreenRefreshRateTimeoutLuxThresholds idleScreenRefreshRateTimeoutLuxThresholds =
+                idleScreenRefreshRateTimeoutConfig.getLuxThresholds();
+
+        if (idleScreenRefreshRateTimeoutLuxThresholds != null) {
+            int previousLux = -1;
+            // Validate that the lux values are in the increasing order
+            for (IdleScreenRefreshRateTimeoutLuxThresholdPoint point :
+                    idleScreenRefreshRateTimeoutLuxThresholds.getPoint()) {
+                int newLux = point.getLux().intValue();
+                if (previousLux >= newLux) {
+                    throw new RuntimeException("Lux values should be in ascending order in the"
+                            + " idle screen refresh rate timeout config");
+                }
+                previousLux = newLux;
+            }
+        }
+    }
+
+    /**
+     * Gets the idle screen refresh rate timeout(in ms) configuration list. For each entry, the lux
+     * value represent the lower bound of the lux range, and the value of the lux in the next
+     * point(INF if not present) represents the upper bound for the corresponding timeout(in ms)
+     */
+    @NonNull
+    public List<IdleScreenRefreshRateTimeoutLuxThresholdPoint>
+            getIdleScreenRefreshRateTimeoutLuxThresholdPoint() {
+        return mIdleScreenRefreshRateTimeoutLuxThresholds;
+    }
+
     /**
      * Extracts a float array from the specified {@link TypedArray}.
      *
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index ce7c224..84eebe8 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -22,6 +22,7 @@
 import static android.Manifest.permission.CAPTURE_VIDEO_OUTPUT;
 import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
 import static android.Manifest.permission.MANAGE_DISPLAYS;
+import static android.Manifest.permission.RESTRICT_DISPLAY_MODES;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
 import static android.hardware.display.DisplayManager.EventsMask;
@@ -75,6 +76,7 @@
 import android.hardware.OverlayProperties;
 import android.hardware.Sensor;
 import android.hardware.SensorManager;
+import android.hardware.devicestate.DeviceState;
 import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.devicestate.DeviceStateManagerInternal;
 import android.hardware.display.AmbientBrightnessDayStats;
@@ -4530,6 +4532,14 @@
             disableConnectedDisplay_enforcePermission();
             DisplayManagerService.this.enableConnectedDisplay(displayId, false);
         }
+
+        @EnforcePermission(RESTRICT_DISPLAY_MODES)
+        @Override // Binder call
+        public void requestDisplayModes(IBinder token, int displayId, @Nullable int[] modeIds) {
+            requestDisplayModes_enforcePermission();
+            DisplayManagerService.this.mDisplayModeDirector.requestDisplayModes(
+                    token, displayId, modeIds);
+        }
     }
 
     private static boolean isValidBrightness(float brightness) {
@@ -5034,30 +5044,22 @@
      * Listens to changes in device state and reports the state to LogicalDisplayMapper.
      */
     class DeviceStateListener implements DeviceStateManager.DeviceStateCallback {
-        // Base state corresponds to the physical state of the device
-        private int mBaseState = DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
 
         @Override
-        public void onStateChanged(int deviceState) {
-            boolean isDeviceStateOverrideActive = deviceState != mBaseState;
+        public void onDeviceStateChanged(DeviceState deviceState) {
             synchronized (mSyncRoot) {
                 // Notify WindowManager that we are about to handle new device state, this should
                 // be sent before any work related to the device state in DisplayManager, so
                 // WindowManager could do implement that depends on the device state and display
                 // changes (serializes device state update and display change events)
                 Message msg = mHandler.obtainMessage(MSG_RECEIVED_DEVICE_STATE);
-                msg.arg1 = deviceState;
+                msg.arg1 = deviceState.getIdentifier();
                 mHandler.sendMessage(msg);
 
                 mLogicalDisplayMapper
-                        .setDeviceStateLocked(deviceState, isDeviceStateOverrideActive);
+                        .setDeviceStateLocked(deviceState.getIdentifier());
             }
         }
-
-        @Override
-        public void onBaseStateChanged(int state) {
-            mBaseState = state;
-        }
     };
 
     private class BrightnessPair {
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/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 5eaaf35..22e4bc5 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -204,6 +204,13 @@
             new SparseArray<>();
 
     /**
+     * If enabled, will not check for {@link Display#FLAG_ROTATES_WITH_CONTENT} in LogicalDisplay
+     * and simply use the {@link DisplayInfo#rotation} supplied by WindowManager via
+     * {@link #setDisplayInfoOverrideFromWindowManagerLocked}
+     */
+    private boolean mAlwaysRotateDisplayDeviceEnabled;
+
+    /**
      * 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
@@ -220,11 +227,11 @@
     private final boolean mIsAnisotropyCorrectionEnabled;
 
     LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) {
-        this(displayId, layerStack, primaryDisplayDevice, false);
+        this(displayId, layerStack, primaryDisplayDevice, false, false);
     }
 
     LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice,
-            boolean isAnisotropyCorrectionEnabled) {
+            boolean isAnisotropyCorrectionEnabled, boolean isAlwaysRotateDisplayDeviceEnabled) {
         mDisplayId = displayId;
         mLayerStack = layerStack;
         mPrimaryDisplayDevice = primaryDisplayDevice;
@@ -236,6 +243,7 @@
         mPowerThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID;
         mBaseDisplayInfo.thermalBrightnessThrottlingDataId = mThermalBrightnessThrottlingDataId;
         mIsAnisotropyCorrectionEnabled = isAnisotropyCorrectionEnabled;
+        mAlwaysRotateDisplayDeviceEnabled = isAlwaysRotateDisplayDeviceEnabled;
     }
 
     public void setDevicePositionLocked(int position) {
@@ -672,7 +680,12 @@
         // The orientation specifies how the physical coordinate system of the display
         // is rotated when the contents of the logical display are rendered.
         int orientation = Surface.ROTATION_0;
-        if ((displayDeviceInfo.flags & DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT) != 0) {
+
+        // FLAG_ROTATES_WITH_CONTENT is now handled in DisplayContent. When the flag
+        // mAlwaysRotateDisplayDeviceEnabled is removed, we should also remove this check for
+        // ROTATES_WITH_CONTENT here and always set the orientation.
+        if ((displayDeviceInfo.flags & DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT) != 0
+                    || mAlwaysRotateDisplayDeviceEnabled) {
             orientation = displayInfo.rotation;
         }
 
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index e092fda..f727eac 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -441,7 +441,7 @@
         mVirtualDeviceDisplayMapping.put(displayDevice.getUniqueId(), virtualDeviceUniqueId);
     }
 
-    void setDeviceStateLocked(int state, boolean isOverrideActive) {
+    void setDeviceStateLocked(int state) {
         if (!mBootCompleted) {
             // The boot animation might still be in progress, we do not want to switch states now
             // as the boot animation would end up with an incorrect size.
@@ -465,7 +465,7 @@
         final boolean wakeDevice = shouldDeviceBeWoken(mPendingDeviceState, mDeviceState,
                 mInteractive, mBootCompleted);
         final boolean sleepDevice = shouldDeviceBePutToSleep(mPendingDeviceState, mDeviceState,
-                isOverrideActive, mInteractive, mBootCompleted);
+                mInteractive, mBootCompleted);
 
         // If all displays are off already, we can just transition here, unless we are trying to
         // wake or sleep the device as part of this transition. In that case defer the final
@@ -513,8 +513,7 @@
             mBootCompleted = true;
             if (mDeviceStateToBeAppliedAfterBoot
                     != DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER) {
-                setDeviceStateLocked(mDeviceStateToBeAppliedAfterBoot,
-                        /* isOverrideActive= */ false);
+                setDeviceStateLocked(mDeviceStateToBeAppliedAfterBoot);
             }
         }
     }
@@ -560,7 +559,6 @@
      *
      * @param pendingState device state we are moving to
      * @param currentState device state we are currently in
-     * @param isOverrideActive if a device state override is currently active or not
      * @param isInteractive if the device is in an interactive state
      * @param isBootCompleted is the device fully booted
      *
@@ -568,13 +566,13 @@
      * @see #setDeviceStateLocked
      */
     @VisibleForTesting
-    boolean shouldDeviceBePutToSleep(int pendingState, int currentState, boolean isOverrideActive,
-            boolean isInteractive, boolean isBootCompleted) {
+    boolean shouldDeviceBePutToSleep(int pendingState, int currentState, boolean isInteractive,
+            boolean isBootCompleted) {
         return currentState != DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER
                 && mDeviceStatesOnWhichToSleep.get(pendingState)
                 && !mDeviceStatesOnWhichToSleep.get(currentState)
-                && !isOverrideActive
-                && isInteractive && isBootCompleted
+                && isInteractive
+                && isBootCompleted
                 && !mFoldSettingProvider.shouldStayAwakeOnFold();
     }
 
@@ -1152,7 +1150,8 @@
     private LogicalDisplay createNewLogicalDisplayLocked(DisplayDevice device, int displayId) {
         final int layerStack = assignLayerStackLocked(displayId);
         final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device,
-                mFlags.isPixelAnisotropyCorrectionInLogicalDisplayEnabled());
+                mFlags.isPixelAnisotropyCorrectionInLogicalDisplayEnabled(),
+                mFlags.isAlwaysRotateDisplayDeviceEnabled());
         display.updateLocked(mDisplayDeviceRepo);
 
         final DisplayInfo info = display.getDisplayInfoLocked();
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index ec5ad7d..bcdb442 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -366,7 +366,8 @@
             if (mSurface == null) {
                 return null;
             }
-            return mSurface.getDefaultSize();
+            final Point surfaceSize = mSurface.getDefaultSize();
+            return isRotatedLocked() ? new Point(surfaceSize.y, surfaceSize.x) : surfaceSize;
         }
 
         @VisibleForTesting
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 15ee937..e1a166e 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -92,9 +92,9 @@
             Flags.FLAG_BRIGHTNESS_INT_RANGE_USER_PERCEPTION,
             Flags::brightnessIntRangeUserPerception);
 
-    private final FlagState mVsyncProximityVote = new FlagState(
-            Flags.FLAG_ENABLE_EXTERNAL_VSYNC_PROXIMITY_VOTE,
-            Flags::enableExternalVsyncProximityVote);
+    private final FlagState mRestrictDisplayModes = new FlagState(
+            Flags.FLAG_ENABLE_RESTRICT_DISPLAY_MODES,
+            Flags::enableRestrictDisplayModes);
 
     private final FlagState mVsyncLowPowerVote = new FlagState(
             Flags.FLAG_ENABLE_VSYNC_LOW_POWER_VOTE,
@@ -116,6 +116,10 @@
             Flags.FLAG_FAST_HDR_TRANSITIONS,
             Flags::fastHdrTransitions);
 
+    private final FlagState mAlwaysRotateDisplayDevice = new FlagState(
+            Flags.FLAG_ALWAYS_ROTATE_DISPLAY_DEVICE,
+            Flags::alwaysRotateDisplayDevice);
+
     private final FlagState mRefreshRateVotingTelemetry = new FlagState(
             Flags.FLAG_REFRESH_RATE_VOTING_TELEMETRY,
             Flags::refreshRateVotingTelemetry
@@ -131,6 +135,11 @@
             Flags::sensorBasedBrightnessThrottling
     );
 
+    private final FlagState mIdleScreenRefreshRateTimeout = new FlagState(
+            Flags.FLAG_IDLE_SCREEN_REFRESH_RATE_TIMEOUT,
+            Flags::idleScreenRefreshRateTimeout
+    );
+
 
     private final FlagState mRefactorDisplayPowerController = new FlagState(
             Flags.FLAG_REFACTOR_DISPLAY_POWER_CONTROLLER,
@@ -233,8 +242,8 @@
         return mBrightnessIntRangeUserPerceptionFlagState.isEnabled();
     }
 
-    public boolean isVsyncProximityVoteEnabled() {
-        return mVsyncProximityVote.isEnabled();
+    public boolean isRestrictDisplayModesEnabled() {
+        return mRestrictDisplayModes.isEnabled();
     }
 
     public boolean isVsyncLowPowerVoteEnabled() {
@@ -260,6 +269,10 @@
         return mFastHdrTransitions.isEnabled();
     }
 
+    public boolean isAlwaysRotateDisplayDeviceEnabled() {
+        return mAlwaysRotateDisplayDevice.isEnabled();
+    }
+
     public boolean isRefreshRateVotingTelemetryEnabled() {
         return mRefreshRateVotingTelemetry.isEnabled();
     }
@@ -272,6 +285,10 @@
         return mSensorBasedBrightnessThrottling.isEnabled();
     }
 
+    public boolean isIdleScreenRefreshRateTimeoutEnabled() {
+        return mIdleScreenRefreshRateTimeout.isEnabled();
+    }
+
     public boolean isRefactorDisplayPowerControllerEnabled() {
         return mRefactorDisplayPowerController.isEnabled();
     }
@@ -294,13 +311,15 @@
         pw.println(" " + mPowerThrottlingClamperFlagState);
         pw.println(" " + mSmallAreaDetectionFlagState);
         pw.println(" " + mBrightnessIntRangeUserPerceptionFlagState);
-        pw.println(" " + mVsyncProximityVote);
+        pw.println(" " + mRestrictDisplayModes);
         pw.println(" " + mBrightnessWearBedtimeModeClamperFlagState);
         pw.println(" " + mAutoBrightnessModesFlagState);
         pw.println(" " + mFastHdrTransitions);
+        pw.println(" " + mAlwaysRotateDisplayDevice);
         pw.println(" " + mRefreshRateVotingTelemetry);
         pw.println(" " + mPixelAnisotropyCorrectionEnabled);
         pw.println(" " + mSensorBasedBrightnessThrottling);
+        pw.println(" " + mIdleScreenRefreshRateTimeout);
         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 9bf36e4..a5f241f 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
@@ -130,9 +130,9 @@
 }
 
 flag {
-    name: "enable_external_vsync_proximity_vote"
+    name: "enable_restrict_display_modes"
     namespace: "display_manager"
-    description: "Feature flag for external vsync proximity vote"
+    description: "Feature flag for restriction display modes api"
     bug: "284866750"
     is_fixed_read_only: true
 }
@@ -178,6 +178,17 @@
 }
 
 flag {
+    name: "always_rotate_display_device"
+    namespace: "display_manager"
+    description: "Use rotation from WindowManager no matter whether FLAG_ROTATES_WITH_CONTENT is set or not"
+    bug: "302326003"
+    is_fixed_read_only: true
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "refresh_rate_voting_telemetry"
     namespace: "display_manager"
     description: "Feature flag for enabling telemetry for refresh rate voting in DisplayManager"
@@ -208,3 +219,11 @@
     bug: "294444204"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "idle_screen_refresh_rate_timeout"
+    namespace: "display_manager"
+    description: "Feature flag for reducing the refresh rate when the screen is idle after a timeout"
+    bug: "310026579"
+    is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/display/mode/BaseModeRefreshRateVote.java b/services/core/java/com/android/server/display/mode/BaseModeRefreshRateVote.java
index c538231..6d750c0 100644
--- a/services/core/java/com/android/server/display/mode/BaseModeRefreshRateVote.java
+++ b/services/core/java/com/android/server/display/mode/BaseModeRefreshRateVote.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display.mode;
 
+import android.annotation.NonNull;
+
 import java.util.Objects;
 
 class BaseModeRefreshRateVote implements Vote {
@@ -31,7 +33,7 @@
     }
 
     @Override
-    public void updateSummary(VoteSummary summary) {
+    public void updateSummary(@NonNull VoteSummary summary) {
         if (summary.appRequestBaseModeRefreshRate == 0f
                 && mAppRequestBaseModeRefreshRate > 0f) {
             summary.appRequestBaseModeRefreshRate = mAppRequestBaseModeRefreshRate;
diff --git a/services/core/java/com/android/server/display/mode/CombinedVote.java b/services/core/java/com/android/server/display/mode/CombinedVote.java
index 4b68791..3cd16bf 100644
--- a/services/core/java/com/android/server/display/mode/CombinedVote.java
+++ b/services/core/java/com/android/server/display/mode/CombinedVote.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display.mode;
 
+import android.annotation.NonNull;
+
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
@@ -28,7 +30,7 @@
     }
 
     @Override
-    public void updateSummary(VoteSummary summary) {
+    public void updateSummary(@NonNull VoteSummary summary) {
         mVotes.forEach(vote -> vote.updateSummary(summary));
     }
 
diff --git a/services/core/java/com/android/server/display/mode/DisableRefreshRateSwitchingVote.java b/services/core/java/com/android/server/display/mode/DisableRefreshRateSwitchingVote.java
index 7f57406..7abb518 100644
--- a/services/core/java/com/android/server/display/mode/DisableRefreshRateSwitchingVote.java
+++ b/services/core/java/com/android/server/display/mode/DisableRefreshRateSwitchingVote.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display.mode;
 
+import android.annotation.NonNull;
+
 import java.util.Objects;
 
 class DisableRefreshRateSwitchingVote implements Vote {
@@ -31,7 +33,7 @@
     }
 
     @Override
-    public void updateSummary(VoteSummary summary) {
+    public void updateSummary(@NonNull VoteSummary summary) {
         summary.disableRefreshRateSwitching =
                 summary.disableRefreshRateSwitching || mDisableRefreshRateSwitching;
     }
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 64cbd54..495ae87 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -41,6 +41,7 @@
 import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
 import android.net.Uri;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.IThermalEventListener;
 import android.os.IThermalService;
 import android.os.Looper;
@@ -80,7 +81,6 @@
 import com.android.server.display.utils.DeviceConfigParsingUtils;
 import com.android.server.display.utils.SensorUtils;
 import com.android.server.sensors.SensorManagerInternal;
-import com.android.server.sensors.SensorManagerInternal.ProximityActiveListener;
 import com.android.server.statusbar.StatusBarManagerInternal;
 
 import java.io.PrintWriter;
@@ -128,9 +128,12 @@
     private final SettingsObserver mSettingsObserver;
     private final DisplayObserver mDisplayObserver;
     private final UdfpsObserver mUdfpsObserver;
-    private final SensorObserver mSensorObserver;
+    private final ProximitySensorObserver mSensorObserver;
     private final HbmObserver mHbmObserver;
     private final SkinThermalStatusObserver mSkinThermalStatusObserver;
+
+    @Nullable
+    private final SystemRequestObserver mSystemRequestObserver;
     private final DeviceConfigParameterProvider mConfigParameterProvider;
     private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings;
 
@@ -203,6 +206,7 @@
             .isDisplaysRefreshRatesSynchronizationEnabled();
         mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled = displayManagerFlags
                 .isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled();
+
         mContext = context;
         mHandler = new DisplayModeDirectorHandler(handler.getLooper());
         mInjector = injector;
@@ -222,10 +226,15 @@
         mVotesStorage = new VotesStorage(this::notifyDesiredDisplayModeSpecsChangedLocked,
                 mVotesStatsReporter);
         mDisplayObserver = new DisplayObserver(context, handler, mVotesStorage);
-        mSensorObserver = new SensorObserver(context, mVotesStorage, injector);
+        mSensorObserver = new ProximitySensorObserver(mVotesStorage, injector);
         mSkinThermalStatusObserver = new SkinThermalStatusObserver(injector, mVotesStorage);
         mHbmObserver = new HbmObserver(injector, mVotesStorage, BackgroundThread.getHandler(),
                 mDeviceConfigDisplaySettings);
+        if (mDvrrSupported && displayManagerFlags.isRestrictDisplayModesEnabled()) {
+            mSystemRequestObserver = new SystemRequestObserver(mVotesStorage);
+        } else {
+            mSystemRequestObserver = null;
+        }
         mAlwaysRespectAppRequest = false;
         mSupportsFrameRateOverride = injector.supportsFrameRateOverride();
     }
@@ -520,6 +529,15 @@
     }
 
     /**
+     * Delegates requestDisplayModes call to SystemRequestObserver
+     */
+    public void requestDisplayModes(IBinder token, int displayId, int[] modeIds) {
+        if (mSystemRequestObserver != null) {
+            mSystemRequestObserver.requestDisplayModes(token, displayId, modeIds);
+        }
+    }
+
+    /**
      * Print the object's state and debug information into the given stream.
      *
      * @param pw The stream to dump information to.
@@ -970,10 +988,10 @@
                     Settings.Global.LOW_POWER_MODE, 0 /*default*/) != 0;
             final Vote vote;
             if (inLowPowerMode && mVsynLowPowerVoteEnabled) {
-                vote = Vote.forSupportedModes(List.of(
-                        new SupportedModesVote.SupportedMode(/* peakRefreshRate= */ 60f,
+                vote = Vote.forSupportedRefreshRates(List.of(
+                        new SupportedRefreshRatesVote.RefreshRates(/* peakRefreshRate= */ 60f,
                                 /* vsyncRate= */ 240f),
-                        new SupportedModesVote.SupportedMode(/* peakRefreshRate= */ 60f,
+                        new SupportedRefreshRatesVote.RefreshRates(/* peakRefreshRate= */ 60f,
                                 /* vsyncRate= */ 60f)
                 ));
             } else if (inLowPowerMode) {
@@ -2158,11 +2176,11 @@
                 }
 
                 if (mVsyncLowLightBlockingVoteEnabled) {
-                    refreshRateSwitchingVote = Vote.forSupportedModesAndDisableRefreshRateSwitching(
+                    refreshRateSwitchingVote = Vote.forSupportedRefreshRatesAndDisableSwitching(
                             List.of(
-                                    new SupportedModesVote.SupportedMode(
+                                    new SupportedRefreshRatesVote.RefreshRates(
                                             /* peakRefreshRate= */ 60f, /* vsyncRate= */ 60f),
-                                    new SupportedModesVote.SupportedMode(
+                                    new SupportedRefreshRatesVote.RefreshRates(
                                             /* peakRefreshRate= */120f, /* vsyncRate= */ 120f)));
                 } else {
                     refreshRateSwitchingVote = Vote.forDisableRefreshRateSwitching();
@@ -2498,116 +2516,6 @@
         }
     }
 
-    protected static final class SensorObserver implements ProximityActiveListener,
-            DisplayManager.DisplayListener {
-        private final String mProximitySensorName = null;
-        private final String mProximitySensorType = Sensor.STRING_TYPE_PROXIMITY;
-
-        private final VotesStorage mVotesStorage;
-        private final Context mContext;
-        private final Injector mInjector;
-        @GuardedBy("mSensorObserverLock")
-        private final SparseBooleanArray mDozeStateByDisplay = new SparseBooleanArray();
-        private final Object mSensorObserverLock = new Object();
-
-        private DisplayManager mDisplayManager;
-        private DisplayManagerInternal mDisplayManagerInternal;
-        @GuardedBy("mSensorObserverLock")
-        private boolean mIsProxActive = false;
-
-        SensorObserver(Context context, VotesStorage votesStorage, Injector injector) {
-            mContext = context;
-            mVotesStorage = votesStorage;
-            mInjector = injector;
-        }
-
-        @Override
-        public void onProximityActive(boolean isActive) {
-            synchronized (mSensorObserverLock) {
-                if (mIsProxActive != isActive) {
-                    mIsProxActive = isActive;
-                    recalculateVotesLocked();
-                }
-            }
-        }
-
-        public void observe() {
-            mDisplayManager = mContext.getSystemService(DisplayManager.class);
-            mDisplayManagerInternal = mInjector.getDisplayManagerInternal();
-
-            final SensorManagerInternal sensorManager = mInjector.getSensorManagerInternal();
-            sensorManager.addProximityActiveListener(BackgroundThread.getExecutor(), this);
-
-            synchronized (mSensorObserverLock) {
-                for (Display d : mInjector.getDisplays()) {
-                    mDozeStateByDisplay.put(d.getDisplayId(), mInjector.isDozeState(d));
-                }
-            }
-            mInjector.registerDisplayListener(this, BackgroundThread.getHandler(),
-                    DisplayManager.EVENT_FLAG_DISPLAY_ADDED
-                            | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                            | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED);
-        }
-
-        private void recalculateVotesLocked() {
-            final Display[] displays = mInjector.getDisplays();
-            for (Display d : displays) {
-                int displayId = d.getDisplayId();
-                Vote vote = null;
-                if (mIsProxActive && !mDozeStateByDisplay.get(displayId)) {
-                    final RefreshRateRange rate =
-                            mDisplayManagerInternal.getRefreshRateForDisplayAndSensor(
-                                    displayId, mProximitySensorName, mProximitySensorType);
-                    if (rate != null) {
-                        vote = Vote.forPhysicalRefreshRates(rate.min, rate.max);
-                    }
-                }
-                mVotesStorage.updateVote(displayId, Vote.PRIORITY_PROXIMITY, vote);
-            }
-        }
-
-        void dump(PrintWriter pw) {
-            pw.println("  SensorObserver");
-            synchronized (mSensorObserverLock) {
-                pw.println("    mIsProxActive=" + mIsProxActive);
-                pw.println("    mDozeStateByDisplay:");
-                for (int i = 0; i < mDozeStateByDisplay.size(); i++) {
-                    final int id = mDozeStateByDisplay.keyAt(i);
-                    final boolean dozed = mDozeStateByDisplay.valueAt(i);
-                    pw.println("      " + id + " -> " + dozed);
-                }
-            }
-        }
-
-        @Override
-        public void onDisplayAdded(int displayId) {
-            boolean isDozeState = mInjector.isDozeState(mInjector.getDisplay(displayId));
-            synchronized (mSensorObserverLock) {
-                mDozeStateByDisplay.put(displayId, isDozeState);
-                recalculateVotesLocked();
-            }
-        }
-
-        @Override
-        public void onDisplayChanged(int displayId) {
-            boolean wasDozeState = mDozeStateByDisplay.get(displayId);
-            synchronized (mSensorObserverLock) {
-                mDozeStateByDisplay.put(displayId,
-                        mInjector.isDozeState(mInjector.getDisplay(displayId)));
-                if (wasDozeState != mDozeStateByDisplay.get(displayId)) {
-                    recalculateVotesLocked();
-                }
-            }
-        }
-
-        @Override
-        public void onDisplayRemoved(int displayId) {
-            synchronized (mSensorObserverLock) {
-                mDozeStateByDisplay.delete(displayId);
-                recalculateVotesLocked();
-            }
-        }
-    }
 
     /**
      * Listens to DisplayManager for HBM status and applies any refresh-rate restrictions for
diff --git a/services/core/java/com/android/server/display/mode/ProximitySensorObserver.java b/services/core/java/com/android/server/display/mode/ProximitySensorObserver.java
new file mode 100644
index 0000000..11418c1
--- /dev/null
+++ b/services/core/java/com/android/server/display/mode/ProximitySensorObserver.java
@@ -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.
+ */
+
+package com.android.server.display.mode;
+
+import android.hardware.Sensor;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerInternal;
+import android.util.SparseBooleanArray;
+import android.view.Display;
+import android.view.SurfaceControl;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.BackgroundThread;
+import com.android.server.sensors.SensorManagerInternal;
+
+import java.io.PrintWriter;
+
+class ProximitySensorObserver implements
+        SensorManagerInternal.ProximityActiveListener,
+        DisplayManager.DisplayListener {
+    private final String mProximitySensorName = null;
+    private final String mProximitySensorType = Sensor.STRING_TYPE_PROXIMITY;
+
+    private final VotesStorage mVotesStorage;
+    private final DisplayModeDirector.Injector mInjector;
+    @GuardedBy("mSensorObserverLock")
+    private final SparseBooleanArray mDozeStateByDisplay = new SparseBooleanArray();
+    private final Object mSensorObserverLock = new Object();
+    private DisplayManagerInternal mDisplayManagerInternal;
+    @GuardedBy("mSensorObserverLock")
+    private boolean mIsProxActive = false;
+
+    ProximitySensorObserver(VotesStorage votesStorage, DisplayModeDirector.Injector injector) {
+        mVotesStorage = votesStorage;
+        mInjector = injector;
+    }
+
+    @Override
+    public void onProximityActive(boolean isActive) {
+        synchronized (mSensorObserverLock) {
+            if (mIsProxActive != isActive) {
+                mIsProxActive = isActive;
+                recalculateVotesLocked();
+            }
+        }
+    }
+
+    void observe() {
+        mDisplayManagerInternal = mInjector.getDisplayManagerInternal();
+
+        final SensorManagerInternal sensorManager = mInjector.getSensorManagerInternal();
+        sensorManager.addProximityActiveListener(BackgroundThread.getExecutor(), this);
+
+        synchronized (mSensorObserverLock) {
+            for (Display d : mInjector.getDisplays()) {
+                mDozeStateByDisplay.put(d.getDisplayId(), mInjector.isDozeState(d));
+            }
+        }
+        mInjector.registerDisplayListener(this, BackgroundThread.getHandler(),
+                DisplayManager.EVENT_FLAG_DISPLAY_ADDED
+                        | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+                        | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED);
+    }
+
+    @GuardedBy("mSensorObserverLock")
+    private void recalculateVotesLocked() {
+        final Display[] displays = mInjector.getDisplays();
+        for (Display d : displays) {
+            int displayId = d.getDisplayId();
+            Vote vote = null;
+            if (mIsProxActive && !mDozeStateByDisplay.get(displayId)) {
+                final SurfaceControl.RefreshRateRange rate =
+                        mDisplayManagerInternal.getRefreshRateForDisplayAndSensor(
+                                displayId, mProximitySensorName, mProximitySensorType);
+                if (rate != null) {
+                    vote = Vote.forPhysicalRefreshRates(rate.min, rate.max);
+                }
+            }
+            mVotesStorage.updateVote(displayId, Vote.PRIORITY_PROXIMITY, vote);
+        }
+    }
+
+    void dump(PrintWriter pw) {
+        pw.println("  SensorObserver");
+        synchronized (mSensorObserverLock) {
+            pw.println("    mIsProxActive=" + mIsProxActive);
+            pw.println("    mDozeStateByDisplay:");
+            for (int i = 0; i < mDozeStateByDisplay.size(); i++) {
+                final int id = mDozeStateByDisplay.keyAt(i);
+                final boolean dozed = mDozeStateByDisplay.valueAt(i);
+                pw.println("      " + id + " -> " + dozed);
+            }
+        }
+    }
+
+    @Override
+    public void onDisplayAdded(int displayId) {
+        boolean isDozeState = mInjector.isDozeState(mInjector.getDisplay(displayId));
+        synchronized (mSensorObserverLock) {
+            mDozeStateByDisplay.put(displayId, isDozeState);
+            recalculateVotesLocked();
+        }
+    }
+
+    @Override
+    public void onDisplayChanged(int displayId) {
+        synchronized (mSensorObserverLock) {
+            boolean wasDozeState = mDozeStateByDisplay.get(displayId);
+            mDozeStateByDisplay.put(displayId,
+                    mInjector.isDozeState(mInjector.getDisplay(displayId)));
+            if (wasDozeState != mDozeStateByDisplay.get(displayId)) {
+                recalculateVotesLocked();
+            }
+        }
+    }
+
+    @Override
+    public void onDisplayRemoved(int displayId) {
+        synchronized (mSensorObserverLock) {
+            mDozeStateByDisplay.delete(displayId);
+            recalculateVotesLocked();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/mode/RefreshRateVote.java b/services/core/java/com/android/server/display/mode/RefreshRateVote.java
index 670b8a1..b96ab3b 100644
--- a/services/core/java/com/android/server/display/mode/RefreshRateVote.java
+++ b/services/core/java/com/android/server/display/mode/RefreshRateVote.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display.mode;
 
+import android.annotation.NonNull;
+
 import java.util.Objects;
 
 
@@ -64,7 +66,7 @@
          *  Vote: min(ignored)     min(applied)  min(applied+physical)  max(applied)  max(ignored)
          */
         @Override
-        public void updateSummary(VoteSummary summary) {
+        public void updateSummary(@NonNull VoteSummary summary) {
             summary.minRenderFrameRate = Math.max(summary.minRenderFrameRate, mMinRefreshRate);
             summary.maxRenderFrameRate = Math.min(summary.maxRenderFrameRate, mMaxRefreshRate);
             // Physical refresh rate cannot be lower than the minimal render frame rate.
@@ -97,7 +99,7 @@
          *  Vote: min(ignored) min(applied)  max(applied+render)     max(applied)  max(ignored)
          */
         @Override
-        public void updateSummary(VoteSummary summary) {
+        public void updateSummary(@NonNull VoteSummary summary) {
             summary.minPhysicalRefreshRate = Math.max(summary.minPhysicalRefreshRate,
                     mMinRefreshRate);
             summary.maxPhysicalRefreshRate = Math.min(summary.maxPhysicalRefreshRate,
diff --git a/services/core/java/com/android/server/display/mode/SizeVote.java b/services/core/java/com/android/server/display/mode/SizeVote.java
index f2f8dc4..f5a5abe 100644
--- a/services/core/java/com/android/server/display/mode/SizeVote.java
+++ b/services/core/java/com/android/server/display/mode/SizeVote.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display.mode;
 
+import android.annotation.NonNull;
+
 import java.util.Objects;
 
 class SizeVote implements Vote {
@@ -48,7 +50,7 @@
     }
 
     @Override
-    public void updateSummary(VoteSummary summary) {
+    public void updateSummary(@NonNull VoteSummary summary) {
         if (mHeight > 0 && mWidth > 0) {
             // For display size, disable refresh rate switching and base mode refresh rate use
             // only the first vote we come across (i.e. the highest priority vote that includes
diff --git a/services/core/java/com/android/server/display/mode/SupportedModesVote.java b/services/core/java/com/android/server/display/mode/SupportedModesVote.java
index 7eebcc0..0cf8311 100644
--- a/services/core/java/com/android/server/display/mode/SupportedModesVote.java
+++ b/services/core/java/com/android/server/display/mode/SupportedModesVote.java
@@ -16,77 +16,42 @@
 
 package com.android.server.display.mode;
 
-import java.util.ArrayList;
+import android.annotation.NonNull;
+
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 
-class SupportedModesVote implements Vote {
+public class SupportedModesVote implements Vote {
 
-    final List<SupportedMode> mSupportedModes;
+    final List<Integer> mModeIds;
 
-    SupportedModesVote(List<SupportedMode> supportedModes) {
-        mSupportedModes = Collections.unmodifiableList(supportedModes);
+    SupportedModesVote(List<Integer> modeIds) {
+        mModeIds = Collections.unmodifiableList(modeIds);
+    }
+    @Override
+    public void updateSummary(@NonNull VoteSummary summary) {
+        if (summary.supportedModeIds == null) {
+            summary.supportedModeIds = mModeIds;
+        } else {
+            summary.supportedModeIds.retainAll(mModeIds);
+        }
     }
 
-    /**
-     * Summary should have subset of supported modes.
-     * If Vote1.supportedModes=(A,B), Vote2.supportedModes=(B,C) then summary.supportedModes=(B)
-     * If summary.supportedModes==null then there is no restriction on supportedModes
-     */
     @Override
-    public void updateSummary(VoteSummary summary) {
-        if (summary.supportedModes == null) {
-            summary.supportedModes = new ArrayList<>(mSupportedModes);
-        } else {
-            summary.supportedModes.retainAll(mSupportedModes);
-        }
+    public String toString() {
+        return "SupportedModesVote{ mModeIds=" + mModeIds + " }";
     }
 
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
         if (!(o instanceof SupportedModesVote that)) return false;
-        return mSupportedModes.equals(that.mSupportedModes);
+        return mModeIds.equals(that.mModeIds);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mSupportedModes);
-    }
-
-    @Override
-    public String toString() {
-        return "SupportedModesVote{ mSupportedModes=" + mSupportedModes + " }";
-    }
-
-    static class SupportedMode {
-        final float mPeakRefreshRate;
-        final float mVsyncRate;
-
-
-        SupportedMode(float peakRefreshRate, float vsyncRate) {
-            mPeakRefreshRate = peakRefreshRate;
-            mVsyncRate = vsyncRate;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (!(o instanceof SupportedMode that)) return false;
-            return Float.compare(that.mPeakRefreshRate, mPeakRefreshRate) == 0
-                    && Float.compare(that.mVsyncRate, mVsyncRate) == 0;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(mPeakRefreshRate, mVsyncRate);
-        }
-
-        @Override
-        public String toString() {
-            return "SupportedMode{ mPeakRefreshRate=" + mPeakRefreshRate
-                    + ", mVsyncRate=" + mVsyncRate + " }";
-        }
+        return Objects.hash(mModeIds);
     }
 }
diff --git a/services/core/java/com/android/server/display/mode/SupportedRefreshRatesVote.java b/services/core/java/com/android/server/display/mode/SupportedRefreshRatesVote.java
new file mode 100644
index 0000000..5305487
--- /dev/null
+++ b/services/core/java/com/android/server/display/mode/SupportedRefreshRatesVote.java
@@ -0,0 +1,94 @@
+/*
+ * 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.mode;
+
+import android.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+class SupportedRefreshRatesVote implements Vote {
+
+    final List<RefreshRates> mRefreshRates;
+
+    SupportedRefreshRatesVote(List<RefreshRates> refreshRates) {
+        mRefreshRates = Collections.unmodifiableList(refreshRates);
+    }
+
+    /**
+     * Summary should have subset of supported modes.
+     * If Vote1.refreshRates=(A,B), Vote2.refreshRates=(B,C)
+     *  then summary.supportedRefreshRates=(B)
+     * If summary.supportedRefreshRates==null then there is no restriction on supportedRefreshRates
+     */
+    @Override
+    public void updateSummary(@NonNull VoteSummary summary) {
+        if (summary.supportedRefreshRates == null) {
+            summary.supportedRefreshRates = new ArrayList<>(mRefreshRates);
+        } else {
+            summary.supportedRefreshRates.retainAll(mRefreshRates);
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof SupportedRefreshRatesVote that)) return false;
+        return mRefreshRates.equals(that.mRefreshRates);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mRefreshRates);
+    }
+
+    @Override
+    public String toString() {
+        return "SupportedRefreshRatesVote{ mSupportedModes=" + mRefreshRates + " }";
+    }
+
+    static class RefreshRates {
+        final float mPeakRefreshRate;
+        final float mVsyncRate;
+
+        RefreshRates(float peakRefreshRate, float vsyncRate) {
+            mPeakRefreshRate = peakRefreshRate;
+            mVsyncRate = vsyncRate;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof RefreshRates that)) return false;
+            return Float.compare(that.mPeakRefreshRate, mPeakRefreshRate) == 0
+                    && Float.compare(that.mVsyncRate, mVsyncRate) == 0;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mPeakRefreshRate, mVsyncRate);
+        }
+
+        @Override
+        public String toString() {
+            return "RefreshRates{ mPeakRefreshRate=" + mPeakRefreshRate
+                    + ", mVsyncRate=" + mVsyncRate + " }";
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/mode/SystemRequestObserver.java b/services/core/java/com/android/server/display/mode/SystemRequestObserver.java
new file mode 100644
index 0000000..15f19cc
--- /dev/null
+++ b/services/core/java/com/android/server/display/mode/SystemRequestObserver.java
@@ -0,0 +1,139 @@
+/*
+ * 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.mode;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * SystemRequestObserver responsible for handling system requests to filter allowable display
+ * modes
+ */
+class SystemRequestObserver {
+    private final VotesStorage mVotesStorage;
+
+    private final IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
+        @Override
+        public void binderDied() {
+            // noop, binderDied(@NonNull IBinder who) is overridden
+        }
+        @Override
+        public void binderDied(@NonNull IBinder who) {
+            removeSystemRequestedVotes(who);
+            who.unlinkToDeath(mDeathRecipient, 0);
+        }
+    };
+
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private final Map<IBinder, SparseArray<List<Integer>>> mDisplaysRestrictions = new HashMap<>();
+
+    SystemRequestObserver(VotesStorage storage) {
+        mVotesStorage = storage;
+    }
+
+    void requestDisplayModes(IBinder token, int displayId, @Nullable int[] modeIds) {
+        if (modeIds == null) {
+            removeSystemRequestedVote(token, displayId);
+        } else {
+            addSystemRequestedVote(token, displayId, modeIds);
+        }
+    }
+
+    private void addSystemRequestedVote(IBinder token, int displayId, @NonNull int[] modeIds) {
+        try {
+            boolean needLinkToDeath = false;
+            List<Integer> modeIdsList = new ArrayList<>();
+            for (int mode: modeIds) {
+                modeIdsList.add(mode);
+            }
+            synchronized (mLock) {
+                SparseArray<List<Integer>> modesByDisplay = mDisplaysRestrictions.get(token);
+                if (modesByDisplay == null) {
+                    needLinkToDeath = true;
+                    modesByDisplay = new SparseArray<>();
+                    mDisplaysRestrictions.put(token, modesByDisplay);
+                }
+
+                modesByDisplay.put(displayId, modeIdsList);
+                updateStorageLocked(displayId);
+            }
+            if (needLinkToDeath) {
+                token.linkToDeath(mDeathRecipient, 0);
+            }
+        } catch (RemoteException re) {
+            removeSystemRequestedVotes(token);
+        }
+    }
+
+    private void removeSystemRequestedVote(IBinder token, int displayId) {
+        boolean needToUnlink = false;
+        synchronized (mLock) {
+            SparseArray<List<Integer>> modesByDisplay = mDisplaysRestrictions.get(token);
+            if (modesByDisplay != null) {
+                modesByDisplay.remove(displayId);
+                needToUnlink = modesByDisplay.size() == 0;
+                updateStorageLocked(displayId);
+            }
+        }
+        if (needToUnlink) {
+            token.unlinkToDeath(mDeathRecipient, 0);
+        }
+    }
+
+    private void removeSystemRequestedVotes(IBinder token) {
+        synchronized (mLock) {
+            SparseArray<List<Integer>> removed = mDisplaysRestrictions.remove(token);
+            if (removed != null) {
+                for (int i = 0; i < removed.size(); i++) {
+                    updateStorageLocked(removed.keyAt(i));
+                }
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void updateStorageLocked(int displayId) {
+        List<Integer> modeIds = new ArrayList<>();
+        boolean[] modesFound = new boolean[1];
+
+        mDisplaysRestrictions.forEach((key, value) -> {
+            List<Integer> modesForDisplay = value.get(displayId);
+            if (modesForDisplay != null) {
+                if (!modesFound[0]) {
+                    modeIds.addAll(modesForDisplay);
+                    modesFound[0] = true;
+                } else {
+                    modeIds.retainAll(modesForDisplay);
+                }
+            }
+        });
+
+        mVotesStorage.updateVote(displayId, Vote.PRIORITY_SYSTEM_REQUESTED_MODES,
+                modesFound[0] ? Vote.forSupportedModes(modeIds) : null);
+    }
+}
diff --git a/services/core/java/com/android/server/display/mode/Vote.java b/services/core/java/com/android/server/display/mode/Vote.java
index e8d5a19..5b987f4 100644
--- a/services/core/java/com/android/server/display/mode/Vote.java
+++ b/services/core/java/com/android/server/display/mode/Vote.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display.mode;
 
+import android.annotation.NonNull;
+
 import java.util.List;
 
 interface Vote {
@@ -91,26 +93,29 @@
     // For concurrent displays we want to limit refresh rate on all displays
     int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 12;
 
+    // For internal application to limit display modes to specific ids
+    int PRIORITY_SYSTEM_REQUESTED_MODES = 13;
+
     // LOW_POWER_MODE force the render frame rate to [0, 60HZ] if
     // Settings.Global.LOW_POWER_MODE is on.
-    int PRIORITY_LOW_POWER_MODE = 13;
+    int PRIORITY_LOW_POWER_MODE = 14;
 
     // PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the
     // higher priority voters' result is a range, it will fix the rate to a single choice.
     // It's used to avoid refresh rate switches in certain conditions which may result in the
     // user seeing the display flickering when the switches occur.
-    int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 14;
+    int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 15;
 
     // Force display to [0, 60HZ] if skin temperature is at or above CRITICAL.
-    int PRIORITY_SKIN_TEMPERATURE = 15;
+    int PRIORITY_SKIN_TEMPERATURE = 16;
 
     // The proximity sensor needs the refresh rate to be locked in order to function, so this is
     // set to a high priority.
-    int PRIORITY_PROXIMITY = 16;
+    int PRIORITY_PROXIMITY = 17;
 
     // The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order
     // to function, so this needs to be the highest priority of all votes.
-    int PRIORITY_UDFPS = 17;
+    int PRIORITY_UDFPS = 18;
 
     // Whenever a new priority is added, remember to update MIN_PRIORITY, MAX_PRIORITY, and
     // APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, as well as priorityToString.
@@ -128,7 +133,7 @@
      */
     int INVALID_SIZE = -1;
 
-    void updateSummary(VoteSummary summary);
+    void updateSummary(@NonNull VoteSummary summary);
 
     static Vote forPhysicalRefreshRates(float minRefreshRate, float maxRefreshRate) {
         return new CombinedVote(
@@ -166,15 +171,22 @@
         return new BaseModeRefreshRateVote(baseModeRefreshRate);
     }
 
-    static Vote forSupportedModes(List<SupportedModesVote.SupportedMode> supportedModes) {
-        return new SupportedModesVote(supportedModes);
+    static Vote forSupportedRefreshRates(
+            List<SupportedRefreshRatesVote.RefreshRates> refreshRates) {
+        return new SupportedRefreshRatesVote(refreshRates);
+    }
+
+    static Vote forSupportedModes(List<Integer> modeIds) {
+        return new SupportedModesVote(modeIds);
     }
 
 
-    static Vote forSupportedModesAndDisableRefreshRateSwitching(
-            List<SupportedModesVote.SupportedMode> supportedModes) {
+
+    static Vote forSupportedRefreshRatesAndDisableSwitching(
+            List<SupportedRefreshRatesVote.RefreshRates> supportedRefreshRates) {
         return new CombinedVote(
-                List.of(forDisableRefreshRateSwitching(), forSupportedModes(supportedModes)));
+                List.of(forDisableRefreshRateSwitching(),
+                        forSupportedRefreshRates(supportedRefreshRates)));
     }
 
     static String priorityToString(int priority) {
diff --git a/services/core/java/com/android/server/display/mode/VoteSummary.java b/services/core/java/com/android/server/display/mode/VoteSummary.java
index 5fc36b5..d4ce892 100644
--- a/services/core/java/com/android/server/display/mode/VoteSummary.java
+++ b/services/core/java/com/android/server/display/mode/VoteSummary.java
@@ -16,6 +16,7 @@
 
 package com.android.server.display.mode;
 
+import android.annotation.Nullable;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.Display;
@@ -39,7 +40,11 @@
     public boolean disableRefreshRateSwitching;
     public float appRequestBaseModeRefreshRate;
 
-    public List<SupportedModesVote.SupportedMode> supportedModes;
+    @Nullable
+    public List<SupportedRefreshRatesVote.RefreshRates> supportedRefreshRates;
+
+    @Nullable
+    public List<Integer> supportedModeIds;
 
     final boolean mIsDisplayResolutionRangeVotingEnabled;
 
@@ -112,6 +117,9 @@
         boolean missingBaseModeRefreshRate = appRequestBaseModeRefreshRate > 0f;
 
         for (Display.Mode mode : modes) {
+            if (!validateRefreshRatesSupported(mode)) {
+                continue;
+            }
             if (!validateModeSupported(mode)) {
                 continue;
             }
@@ -253,21 +261,37 @@
     }
 
     private boolean validateModeSupported(Display.Mode mode) {
-        if (supportedModes == null || !mSupportedModesVoteEnabled) {
+        if (supportedModeIds == null || !mSupportedModesVoteEnabled) {
             return true;
         }
-        for (SupportedModesVote.SupportedMode supportedMode : supportedModes) {
-            if (equalsWithinFloatTolerance(mode.getRefreshRate(), supportedMode.mPeakRefreshRate)
-                    && equalsWithinFloatTolerance(mode.getVsyncRate(), supportedMode.mVsyncRate)) {
+        if (supportedModeIds.contains(mode.getModeId())) {
+            return true;
+        }
+        if (mLoggingEnabled) {
+            Slog.w(TAG, "Discarding mode " + mode.getModeId()
+                    + ", supportedMode not found"
+                    + ": mode.modeId=" + mode.getModeId()
+                    + ", supportedModeIds=" + supportedModeIds);
+        }
+        return false;
+    }
+
+    private boolean validateRefreshRatesSupported(Display.Mode mode) {
+        if (supportedRefreshRates == null || !mSupportedModesVoteEnabled) {
+            return true;
+        }
+        for (SupportedRefreshRatesVote.RefreshRates refreshRates : this.supportedRefreshRates) {
+            if (equalsWithinFloatTolerance(mode.getRefreshRate(), refreshRates.mPeakRefreshRate)
+                    && equalsWithinFloatTolerance(mode.getVsyncRate(), refreshRates.mVsyncRate)) {
                 return true;
             }
         }
         if (mLoggingEnabled) {
             Slog.w(TAG, "Discarding mode " + mode.getModeId()
-                    + ", supportedMode not found"
+                    + ", supportedRefreshRates not found"
                     + ": mode.refreshRate=" + mode.getRefreshRate()
                     + ", mode.vsyncRate=" + mode.getVsyncRate()
-                    + ", supportedModes=" + supportedModes);
+                    + ", supportedRefreshRates=" + supportedRefreshRates);
         }
         return false;
     }
@@ -298,7 +322,8 @@
             return false;
         }
 
-        if (supportedModes != null && mSupportedModesVoteEnabled && supportedModes.isEmpty()) {
+        if (supportedRefreshRates != null && mSupportedModesVoteEnabled
+                && supportedRefreshRates.isEmpty()) {
             if (mLoggingEnabled) {
                 Slog.w(TAG, "Vote summary resulted in empty set (empty supportedModes)");
             }
@@ -345,7 +370,8 @@
         minHeight = 0;
         disableRefreshRateSwitching = false;
         appRequestBaseModeRefreshRate = 0f;
-        supportedModes = null;
+        supportedRefreshRates = null;
+        supportedModeIds = null;
         if (mLoggingEnabled) {
             Slog.i(TAG, "Summary reset: " + this);
         }
@@ -367,7 +393,8 @@
                 + ", minHeight=" + minHeight
                 + ", disableRefreshRateSwitching=" + disableRefreshRateSwitching
                 + ", appRequestBaseModeRefreshRate=" + appRequestBaseModeRefreshRate
-                + ", supportedModes=" + supportedModes
+                + ", supportedRefreshRates=" + supportedRefreshRates
+                + ", supportedModeIds=" + supportedModeIds
                 + ", mIsDisplayResolutionRangeVotingEnabled="
                 + mIsDisplayResolutionRangeVotingEnabled
                 + ", mSupportedModesVoteEnabled=" + mSupportedModesVoteEnabled
diff --git a/services/core/java/com/android/server/display/mode/VotesStatsReporter.java b/services/core/java/com/android/server/display/mode/VotesStatsReporter.java
index e80b9451..7562a52 100644
--- a/services/core/java/com/android/server/display/mode/VotesStatsReporter.java
+++ b/services/core/java/com/android/server/display/mode/VotesStatsReporter.java
@@ -117,11 +117,11 @@
             maxRefreshRate = (int) physicalVote.mMaxRefreshRate;
         } else if (!ignoreRenderRate && (vote instanceof RefreshRateVote.RenderVote renderVote)) {
             maxRefreshRate = (int)  renderVote.mMaxRefreshRate;
-        } else if (vote instanceof SupportedModesVote supportedModesVote) {
-            // SupportedModesVote limits mode by specific refreshRates, so highest rr is allowed
+        } else if (vote instanceof SupportedRefreshRatesVote refreshRatesVote) {
+            // SupportedRefreshRatesVote limits mode by refreshRates, so highest rr is allowed
             maxRefreshRate = 0;
-            for (SupportedModesVote.SupportedMode mode : supportedModesVote.mSupportedModes) {
-                maxRefreshRate = Math.max(maxRefreshRate, (int) mode.mPeakRefreshRate);
+            for (SupportedRefreshRatesVote.RefreshRates rr : refreshRatesVote.mRefreshRates) {
+                maxRefreshRate = Math.max(maxRefreshRate, (int) rr.mPeakRefreshRate);
             }
         } else if (vote instanceof CombinedVote combinedVote) {
             for (Vote subVote: combinedVote.mVotes) {
diff --git a/services/core/java/com/android/server/display/mode/VotesStorage.java b/services/core/java/com/android/server/display/mode/VotesStorage.java
index 56c7c18..6becf1c 100644
--- a/services/core/java/com/android/server/display/mode/VotesStorage.java
+++ b/services/core/java/com/android/server/display/mode/VotesStorage.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.util.IntArray;
 import android.util.Slog;
 import android.util.SparseArray;
 
@@ -124,6 +125,44 @@
         }
     }
 
+    /** removes all votes with certain priority from vote storage */
+    void removeAllVotesForPriority(int priority) {
+        if (mLoggingEnabled) {
+            Slog.i(TAG, "removeAllVotesForPriority(priority="
+                    + Vote.priorityToString(priority) + ")");
+        }
+        if (priority < Vote.MIN_PRIORITY || priority > Vote.MAX_PRIORITY) {
+            Slog.w(TAG, "Received an invalid priority, ignoring:"
+                    + " priority=" + Vote.priorityToString(priority));
+            return;
+        }
+        IntArray removedVotesDisplayIds = new IntArray();
+        synchronized (mStorageLock) {
+            int size = mVotesByDisplay.size();
+            for (int i = 0; i < size; i++) {
+                SparseArray<Vote> votes = mVotesByDisplay.valueAt(i);
+                if (votes.get(priority) != null) {
+                    votes.remove(priority);
+                    removedVotesDisplayIds.add(mVotesByDisplay.keyAt(i));
+                }
+            }
+        }
+        if (mLoggingEnabled) {
+            Slog.i(TAG, "Removed votes with priority=" + priority
+                    + " for displays=" + removedVotesDisplayIds);
+        }
+        int removedVotesSize = removedVotesDisplayIds.size();
+        if (removedVotesSize > 0) {
+            if (mVotesStatsReporter != null) {
+                for (int i = 0; i < removedVotesSize; i++) {
+                    mVotesStatsReporter.reportVoteChanged(
+                            removedVotesDisplayIds.get(i), priority, null);
+                }
+            }
+            mListener.onChanged();
+        }
+    }
+
     /** dump class values, for debugging */
     void dump(@NonNull PrintWriter pw) {
         SparseArray<SparseArray<Vote>> votesByDisplayLocal = new SparseArray<>();
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index e6bf2c9..3fafca8 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -266,7 +266,11 @@
                 throw new RuntimeException(e);
             }
         }
+        updateConfiguration(grammaticalGender, userId);
+        Trace.endSection();
+    }
 
+    private void updateConfiguration(int grammaticalGender, int userId) {
         try {
             Configuration config = new Configuration();
             int preValue = config.getGrammaticalGender();
@@ -280,7 +284,6 @@
         } catch (RemoteException e) {
             Log.w(TAG, "Can not update configuration", e);
         }
-        Trace.endSection();
     }
 
     public int getSystemGrammaticalGender(AttributionSource attributionSource, int userId) {
@@ -372,7 +375,9 @@
                 if (mGrammaticalGenderCache.indexOfKey(userId) < 0) {
                     try (FileInputStream in = new FileInputStream(file)) {
                         final TypedXmlPullParser parser = Xml.resolvePullParser(in);
-                        mGrammaticalGenderCache.put(userId, getGrammaticalGenderFromXml(parser));
+                        int grammaticalGender = getGrammaticalGenderFromXml(parser);
+                        mGrammaticalGenderCache.put(userId, grammaticalGender);
+                        updateConfiguration(grammaticalGender, userId);
                     } catch (IOException | XmlPullParserException e) {
                         Log.e(TAG, "Failed to parse XML configuration from " + file, e);
                     }
diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
index b8ae737..f21fd41 100644
--- a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
+++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
@@ -146,6 +146,10 @@
         SPLIT_SCREEN_NAVIGATION(
                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SPLIT_SCREEN_NAVIGATION,
                 "SPLIT_SCREEN_NAVIGATION"),
+
+        CHANGE_SPLITSCREEN_FOCUS(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__CHANGE_SPLITSCREEN_FOCUS,
+                "CHANGE_SPLITSCREEN_FOCUS"),
         TRIGGER_BUG_REPORT(
                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TRIGGER_BUG_REPORT,
                 "TRIGGER_BUG_REPORT"),
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
index c7b60da..dd6433d 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
@@ -19,11 +19,13 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.content.Context;
 import android.content.pm.UserInfo;
 import android.os.Handler;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.DirectBootAwareness;
 import com.android.server.LocalServices;
 import com.android.server.pm.UserManagerInternal;
 
@@ -67,7 +69,7 @@
         AdditionalSubtypeUtils.save(map, inputMethodMap, userId);
     }
 
-    static void initialize(@NonNull Handler handler) {
+    static void initialize(@NonNull Handler handler, @NonNull Context context) {
         final UserManagerInternal userManagerInternal =
                 LocalServices.getService(UserManagerInternal.class);
         handler.post(() -> {
@@ -79,8 +81,16 @@
                             handler.post(() -> {
                                 synchronized (ImfLock.class) {
                                     if (!sPerUserMap.contains(userId)) {
-                                        sPerUserMap.put(userId,
-                                                AdditionalSubtypeUtils.load(userId));
+                                        final AdditionalSubtypeMap additionalSubtypeMap =
+                                                AdditionalSubtypeUtils.load(userId);
+                                        sPerUserMap.put(userId, additionalSubtypeMap);
+                                        final InputMethodSettings settings =
+                                                InputMethodManagerService
+                                                        .queryInputMethodServicesInternal(context,
+                                                                userId,
+                                                                additionalSubtypeMap,
+                                                                DirectBootAwareness.AUTO);
+                                        InputMethodSettingsRepository.put(userId, settings);
                                     }
                                 }
                             });
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index fef5661..950d6aa 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -284,9 +284,12 @@
     final Context mContext;
     final Resources mRes;
     private final Handler mHandler;
-    @NonNull
+
     @MultiUserUnawareField
-    private InputMethodSettings mSettings;
+    @UserIdInt
+    @GuardedBy("ImfLock.class")
+    private int mCurrentUserId;
+
     @MultiUserUnawareField
     final SettingsObserver mSettingsObserver;
     final WindowManagerInternal mWindowManagerInternal;
@@ -491,7 +494,7 @@
     @GuardedBy("ImfLock.class")
     @Nullable
     InputMethodInfo queryInputMethodForCurrentUserLocked(@NonNull String imeId) {
-        return mSettings.getMethodMap().get(imeId);
+        return InputMethodSettingsRepository.get(mCurrentUserId).getMethodMap().get(imeId);
     }
 
     /**
@@ -555,10 +558,13 @@
     private InputMethodSubtype mCurrentSubtype;
 
     /**
-     * {@code true} if the IME has not been mostly hidden via {@link android.view.InsetsController}
+     * Map of window perceptible states indexed by their associated window tokens.
+     *
+     * The value {@code true} indicates that IME has not been mostly hidden via
+     * {@link android.view.InsetsController} for the given window.
      */
-    @MultiUserUnawareField
-    private boolean mCurPerceptible;
+    @GuardedBy("ImfLock.class")
+    private final WeakHashMap<IBinder, Boolean> mFocusedWindowPerceptible = new WeakHashMap<>();
 
     /**
      * Set to true if our ServiceConnection is currently actively bound to
@@ -809,7 +815,8 @@
                     InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches();
                 } else {
                     boolean enabledChanged = false;
-                    String newEnabled = mSettings.getEnabledInputMethodsStr();
+                    String newEnabled = InputMethodSettingsRepository.get(mCurrentUserId)
+                            .getEnabledInputMethodsStr();
                     if (!mLastEnabled.equals(newEnabled)) {
                         mLastEnabled = newEnabled;
                         enabledChanged = true;
@@ -841,9 +848,11 @@
                 // sender userId can be a real user ID or USER_ALL.
                 final int senderUserId = pendingResult.getSendingUserId();
                 if (senderUserId != UserHandle.USER_ALL) {
-                    if (senderUserId != mSettings.getUserId()) {
-                        // A background user is trying to hide the dialog. Ignore.
-                        return;
+                    synchronized (ImfLock.class) {
+                        if (senderUserId != mCurrentUserId) {
+                            // A background user is trying to hide the dialog. Ignore.
+                            return;
+                        }
                     }
                 }
                 mMenuController.hideInputMethodMenu();
@@ -867,7 +876,15 @@
             if (!mSystemReady) {
                 return;
             }
-            buildInputMethodListLocked(true);
+            for (int userId : mUserManagerInternal.getUserIds()) {
+                final InputMethodSettings settings = queryInputMethodServicesInternal(
+                                mContext,
+                                userId,
+                                AdditionalSubtypeMapRepository.get(userId),
+                                DirectBootAwareness.AUTO);
+                InputMethodSettingsRepository.put(userId, settings);
+            }
+            postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */);
             // If the locale is changed, needs to reset the default ime
             resetDefaultImeLocked(mContext);
             updateFromSettingsLocked(true);
@@ -927,7 +944,7 @@
         @GuardedBy("ImfLock.class")
         private boolean isChangingPackagesOfCurrentUserLocked() {
             final int userId = getChangingUserId();
-            final boolean retval = userId == mSettings.getUserId();
+            final boolean retval = userId == mCurrentUserId;
             if (DEBUG) {
                 if (!retval) {
                     Slog.d(TAG, "--- ignore this call back from a background user: " + userId);
@@ -942,8 +959,10 @@
                 if (!isChangingPackagesOfCurrentUserLocked()) {
                     return false;
                 }
-                String curInputMethodId = mSettings.getSelectedInputMethod();
-                final List<InputMethodInfo> methodList = mSettings.getMethodList();
+                final InputMethodSettings settings =
+                        InputMethodSettingsRepository.get(mCurrentUserId);
+                String curInputMethodId = settings.getSelectedInputMethod();
+                final List<InputMethodInfo> methodList = settings.getMethodList();
                 final int numImes = methodList.size();
                 if (curInputMethodId != null) {
                     for (int i = 0; i < numImes; i++) {
@@ -1060,16 +1079,10 @@
         private void onFinishPackageChangesInternal() {
             synchronized (ImfLock.class) {
                 final int userId = getChangingUserId();
-                final boolean isCurrentUser = (userId == mSettings.getUserId());
+                final boolean isCurrentUser = (userId == mCurrentUserId);
                 final AdditionalSubtypeMap additionalSubtypeMap =
                         AdditionalSubtypeMapRepository.get(userId);
-                final InputMethodSettings settings;
-                if (isCurrentUser) {
-                    settings = mSettings;
-                } else {
-                    settings = queryInputMethodServicesInternal(mContext, userId,
-                            additionalSubtypeMap, DirectBootAwareness.AUTO);
-                }
+                final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
 
                 InputMethodInfo curIm = null;
                 String curInputMethodId = settings.getSelectedInputMethod();
@@ -1113,13 +1126,18 @@
                     AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
                             settings.getMethodMap());
                 }
-
-                if (!isCurrentUser
-                        || !(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) {
+                if (isCurrentUser
+                        && !(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) {
                     return;
                 }
 
-                buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
+                final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+                        userId, newAdditionalSubtypeMap, DirectBootAwareness.AUTO);
+                InputMethodSettingsRepository.put(userId, newSettings);
+                if (!isCurrentUser) {
+                    return;
+                }
+                postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
 
                 boolean changed = false;
 
@@ -1271,17 +1289,18 @@
 
     void onUnlockUser(@UserIdInt int userId) {
         synchronized (ImfLock.class) {
-            final int currentUserId = mSettings.getUserId();
             if (DEBUG) {
-                Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + currentUserId);
+                Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + mCurrentUserId);
             }
-            if (userId != currentUserId) {
+            if (!mSystemReady) {
                 return;
             }
-            mSettings = InputMethodSettings.createEmptyMap(userId);
-            if (mSystemReady) {
+            final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+                    userId, AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO);
+            InputMethodSettingsRepository.put(userId, newSettings);
+            if (mCurrentUserId == userId) {
                 // We need to rebuild IMEs.
-                buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
+                postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
                 updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
             }
         }
@@ -1347,19 +1366,20 @@
 
         mShowOngoingImeSwitcherForPhones = false;
 
-        AdditionalSubtypeMapRepository.initialize(mHandler);
+        // InputMethodSettingsRepository should be initialized before buildInputMethodListLocked
+        InputMethodSettingsRepository.initialize(mHandler, mContext);
+        AdditionalSubtypeMapRepository.initialize(mHandler, mContext);
 
-        final int userId = mActivityManagerInternal.getCurrentUserId();
+        mCurrentUserId = mActivityManagerInternal.getCurrentUserId();
 
-        // mSettings should be created before buildInputMethodListLocked
-        mSettings = InputMethodSettings.createEmptyMap(userId);
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
 
         mSwitchingController =
                 InputMethodSubtypeSwitchingController.createInstanceLocked(context,
-                        mSettings.getMethodMap(), userId);
+                        settings.getMethodMap(), settings.getUserId());
         mHardwareKeyboardShortcutController =
-                new HardwareKeyboardShortcutController(mSettings.getMethodMap(),
-                        mSettings.getUserId());
+                new HardwareKeyboardShortcutController(settings.getMethodMap(),
+                        settings.getUserId());
         mMenuController = new InputMethodMenuController(this);
         mBindingController =
                 bindingControllerForTesting != null
@@ -1392,7 +1412,7 @@
     @GuardedBy("ImfLock.class")
     @UserIdInt
     int getCurrentImeUserIdLocked() {
-        return mSettings.getUserId();
+        return mCurrentUserId;
     }
 
     private final class InkWindowInitializer implements Runnable {
@@ -1428,12 +1448,13 @@
     private void resetDefaultImeLocked(Context context) {
         // Do not reset the default (current) IME when it is a 3rd-party IME
         String selectedMethodId = getSelectedMethodIdLocked();
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
         if (selectedMethodId != null
-                && !mSettings.getMethodMap().get(selectedMethodId).isSystem()) {
+                && !settings.getMethodMap().get(selectedMethodId).isSystem()) {
             return;
         }
         final List<InputMethodInfo> suitableImes = InputMethodInfoUtils.getDefaultEnabledImes(
-                context, mSettings.getEnabledInputMethodList());
+                context, settings.getEnabledInputMethodList());
         if (suitableImes.isEmpty()) {
             Slog.i(TAG, "No default found");
             return;
@@ -1489,7 +1510,7 @@
             IInputMethodClientInvoker clientToBeReset) {
         if (DEBUG) {
             Slog.d(TAG, "Switching user stage 1/3. newUserId=" + newUserId
-                    + " currentUserId=" + mSettings.getUserId());
+                    + " currentUserId=" + mCurrentUserId);
         }
 
         maybeInitImeNavbarConfigLocked(newUserId);
@@ -1497,8 +1518,9 @@
         // ContentObserver should be registered again when the user is changed
         mSettingsObserver.registerContentObserverLocked(newUserId);
 
-        mSettings = InputMethodSettings.createEmptyMap(newUserId);
-        final String defaultImiId = mSettings.getSelectedInputMethod();
+        mCurrentUserId = newUserId;
+        final String defaultImiId = SecureSettingsWrapper.getString(
+                Settings.Secure.DEFAULT_INPUT_METHOD, null, newUserId);
 
         if (DEBUG) {
             Slog.d(TAG, "Switching user stage 2/3. newUserId=" + newUserId
@@ -1515,8 +1537,10 @@
         // The mSystemReady flag is set during boot phase,
         // and user switch would not happen at that time.
         resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_USER);
-        buildInputMethodListLocked(initialUserSwitch);
-        if (TextUtils.isEmpty(mSettings.getSelectedInputMethod())) {
+
+        final InputMethodSettings newSettings = InputMethodSettingsRepository.get(newUserId);
+        postInputMethodSettingUpdatedLocked(initialUserSwitch /* resetDefaultEnabledIme */);
+        if (TextUtils.isEmpty(newSettings.getSelectedInputMethod())) {
             // This is the first time of the user switch and
             // set the current ime to the proper one.
             resetDefaultImeLocked(mContext);
@@ -1526,12 +1550,12 @@
         if (initialUserSwitch) {
             InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
                     getPackageManagerForUser(mContext, newUserId),
-                    mSettings.getEnabledInputMethodList());
+                    newSettings.getEnabledInputMethodList());
         }
 
         if (DEBUG) {
             Slog.d(TAG, "Switching user stage 3/3. newUserId=" + newUserId
-                    + " selectedIme=" + mSettings.getSelectedInputMethod());
+                    + " selectedIme=" + newSettings.getSelectedInputMethod());
         }
 
         if (mIsInteractive && clientToBeReset != null) {
@@ -1554,7 +1578,7 @@
             }
             if (!mSystemReady) {
                 mSystemReady = true;
-                final int currentUserId = mSettings.getUserId();
+                final int currentUserId = mCurrentUserId;
                 mStatusBarManagerInternal =
                         LocalServices.getService(StatusBarManagerInternal.class);
                 hideStatusBarIconLocked();
@@ -1575,7 +1599,7 @@
                     // the "mImeDrawsImeNavBarResLazyInitFuture" field.
                     synchronized (ImfLock.class) {
                         mImeDrawsImeNavBarResLazyInitFuture = null;
-                        if (currentUserId != mSettings.getUserId()) {
+                        if (currentUserId != mCurrentUserId) {
                             // This means that the current user is already switched to other user
                             // before the background task is executed. In this scenario the relevant
                             // field should already be initialized.
@@ -1594,13 +1618,19 @@
                         UserHandle.ALL, broadcastFilterForAllUsers, null, null,
                         Context.RECEIVER_EXPORTED);
 
-                final String defaultImiId = mSettings.getSelectedInputMethod();
+                final String defaultImiId = SecureSettingsWrapper.getString(
+                        Settings.Secure.DEFAULT_INPUT_METHOD, null, currentUserId);
                 final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
-                buildInputMethodListLocked(!imeSelectedOnBoot /* resetDefaultEnabledIme */);
+                final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+                        currentUserId, AdditionalSubtypeMapRepository.get(currentUserId),
+                        DirectBootAwareness.AUTO);
+                InputMethodSettingsRepository.put(currentUserId, newSettings);
+                postInputMethodSettingUpdatedLocked(
+                        !imeSelectedOnBoot /* resetDefaultEnabledIme */);
                 updateFromSettingsLocked(true);
                 InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
                         getPackageManagerForUser(mContext, currentUserId),
-                        mSettings.getEnabledInputMethodList());
+                        newSettings.getEnabledInputMethodList());
             }
         }
     }
@@ -1647,7 +1677,7 @@
         }
         synchronized (ImfLock.class) {
             final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId,
-                    mSettings.getUserId(), null);
+                    mCurrentUserId, null);
             if (resolvedUserIds.length != 1) {
                 return Collections.emptyList();
             }
@@ -1670,7 +1700,7 @@
         }
         synchronized (ImfLock.class) {
             final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId,
-                    mSettings.getUserId(), null);
+                    mCurrentUserId, null);
             if (resolvedUserIds.length != 1) {
                 return Collections.emptyList();
             }
@@ -1698,14 +1728,12 @@
             }
 
             // Check if selected IME of current user supports handwriting.
-            if (userId == mSettings.getUserId()) {
+            if (userId == mCurrentUserId) {
                 return mBindingController.supportsStylusHandwriting()
                         && (!connectionless
                                 || mBindingController.supportsConnectionlessStylusHandwriting());
             }
-            //TODO(b/197848765): This can be optimized by caching multi-user methodMaps/methodList.
-            //TODO(b/210039666): use cache.
-            final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+            final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
             final InputMethodInfo imi = settings.getMethodMap().get(
                     settings.getSelectedInputMethod());
             return imi != null && imi.supportsStylusHandwriting()
@@ -1729,9 +1757,8 @@
     private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId,
             @DirectBootAwareness int directBootAwareness, int callingUid) {
         final InputMethodSettings settings;
-        if (userId == mSettings.getUserId()
-                && directBootAwareness == DirectBootAwareness.AUTO) {
-            settings = mSettings;
+        if (directBootAwareness == DirectBootAwareness.AUTO) {
+            settings = InputMethodSettingsRepository.get(userId);
         } else {
             final AdditionalSubtypeMap additionalSubtypeMap =
                     AdditionalSubtypeMapRepository.get(userId);
@@ -1749,15 +1776,8 @@
     @GuardedBy("ImfLock.class")
     private List<InputMethodInfo> getEnabledInputMethodListLocked(@UserIdInt int userId,
             int callingUid) {
-        final ArrayList<InputMethodInfo> methodList;
-        final InputMethodSettings settings;
-        if (userId == mSettings.getUserId()) {
-            methodList = mSettings.getEnabledInputMethodList();
-            settings = mSettings;
-        } else {
-            settings = queryMethodMapForUserLocked(userId);
-            methodList = settings.getEnabledInputMethodList();
-        }
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+        final ArrayList<InputMethodInfo> methodList = settings.getEnabledInputMethodList();
         // filter caller's access to input methods
         methodList.removeIf(imi ->
                 !canCallerAccessInputMethod(imi.getPackageName(), callingUid, userId, settings));
@@ -1815,22 +1835,7 @@
     @GuardedBy("ImfLock.class")
     private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(String imiId,
             boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId, int callingUid) {
-        if (userId == mSettings.getUserId()) {
-            final InputMethodInfo imi;
-            String selectedMethodId = getSelectedMethodIdLocked();
-            if (imiId == null && selectedMethodId != null) {
-                imi = mSettings.getMethodMap().get(selectedMethodId);
-            } else {
-                imi = mSettings.getMethodMap().get(imiId);
-            }
-            if (imi == null || !canCallerAccessInputMethod(
-                    imi.getPackageName(), callingUid, userId, mSettings)) {
-                return Collections.emptyList();
-            }
-            return mSettings.getEnabledInputMethodSubtypeList(
-                    imi, allowsImplicitlyEnabledSubtypes);
-        }
-        final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
         final InputMethodInfo imi = settings.getMethodMap().get(imiId);
         if (imi == null) {
             return Collections.emptyList();
@@ -2010,7 +2015,7 @@
 
         final boolean restarting = !initial;
         final Binder startInputToken = new Binder();
-        final StartInputInfo info = new StartInputInfo(mSettings.getUserId(),
+        final StartInputInfo info = new StartInputInfo(mCurrentUserId,
                 getCurTokenLocked(),
                 mCurTokenDisplayId, getCurIdLocked(), startInputReason, restarting,
                 UserHandle.getUserId(mCurClient.mUid),
@@ -2024,9 +2029,9 @@
         // same-user scenarios.
         // That said ignoring cross-user scenario will never affect IMEs that do not have
         // INTERACT_ACROSS_USERS(_FULL) permissions, which is actually almost always the case.
-        if (mSettings.getUserId() == UserHandle.getUserId(
+        if (mCurrentUserId == UserHandle.getUserId(
                 mCurClient.mUid)) {
-            mPackageManagerInternal.grantImplicitAccess(mSettings.getUserId(),
+            mPackageManagerInternal.grantImplicitAccess(mCurrentUserId,
                     null /* intent */, UserHandle.getAppId(getCurMethodUidLocked()),
                     mCurClient.mUid, true /* direct */);
         }
@@ -2049,7 +2054,8 @@
         }
 
         String curId = getCurIdLocked();
-        final InputMethodInfo curInputMethodInfo = mSettings.getMethodMap().get(curId);
+        final InputMethodInfo curInputMethodInfo = InputMethodSettingsRepository.get(mCurrentUserId)
+                .getMethodMap().get(curId);
         final boolean suppressesSpellChecker =
                 curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker();
         final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions =
@@ -2229,17 +2235,18 @@
             return currentMethodId;
         }
 
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
         final int oldDeviceId = mDeviceIdToShowIme;
         mDeviceIdToShowIme = mVdmInternal.getDeviceIdForDisplayId(mDisplayIdToShowIme);
         if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
             if (oldDeviceId == DEVICE_ID_DEFAULT) {
                 return currentMethodId;
             }
-            final String defaultDeviceMethodId = mSettings.getSelectedDefaultDeviceInputMethod();
+            final String defaultDeviceMethodId = settings.getSelectedDefaultDeviceInputMethod();
             if (DEBUG) {
                 Slog.v(TAG, "Restoring default device input method: " + defaultDeviceMethodId);
             }
-            mSettings.putSelectedDefaultDeviceInputMethod(null);
+            settings.putSelectedDefaultDeviceInputMethod(null);
             return defaultDeviceMethodId;
         }
 
@@ -2247,7 +2254,7 @@
                 mVirtualDeviceMethodMap.get(mDeviceIdToShowIme, currentMethodId);
         if (Objects.equals(deviceMethodId, currentMethodId)) {
             return currentMethodId;
-        } else if (!mSettings.getMethodMap().containsKey(deviceMethodId)) {
+        } else if (!settings.getMethodMap().containsKey(deviceMethodId)) {
             if (DEBUG) {
                 Slog.v(TAG, "Disabling IME on virtual device with id " + mDeviceIdToShowIme
                         + " because its custom input method is not available: " + deviceMethodId);
@@ -2259,7 +2266,7 @@
             if (DEBUG) {
                 Slog.v(TAG, "Storing default device input method " + currentMethodId);
             }
-            mSettings.putSelectedDefaultDeviceInputMethod(currentMethodId);
+            settings.putSelectedDefaultDeviceInputMethod(currentMethodId);
         }
         if (DEBUG) {
             Slog.v(TAG, "Switching current input method from " + currentMethodId
@@ -2289,7 +2296,8 @@
         if (isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags)) {
             return false;
         }
-        final InputMethodInfo imi = mSettings.getMethodMap().get(selectedMethodId);
+        final InputMethodInfo imi = InputMethodSettingsRepository.get(mCurrentUserId)
+                .getMethodMap().get(selectedMethodId);
         if (imi == null) {
             return false;
         }
@@ -2633,7 +2641,7 @@
                 } else if (packageName != null) {
                     if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
                     final PackageManager userAwarePackageManager =
-                            getPackageManagerForUser(mContext, mSettings.getUserId());
+                            getPackageManagerForUser(mContext, mCurrentUserId);
                     ApplicationInfo applicationInfo = null;
                     try {
                         applicationInfo = userAwarePackageManager.getApplicationInfo(packageName,
@@ -2695,7 +2703,7 @@
             return false;
         }
         if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded()
-                && mWindowManagerInternal.isKeyguardSecure(mSettings.getUserId())) {
+                && mWindowManagerInternal.isKeyguardSecure(mCurrentUserId)) {
             return false;
         }
         if ((visibility & InputMethodService.IME_ACTIVE) == 0
@@ -2712,7 +2720,8 @@
             return false;
         }
 
-        List<InputMethodInfo> imes = mSettings.getEnabledInputMethodListWithFilter(
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+        List<InputMethodInfo> imes = settings.getEnabledInputMethodListWithFilter(
                 InputMethodInfo::shouldShowInInputMethodPicker);
         final int numImes = imes.size();
         if (numImes > 2) return true;
@@ -2724,7 +2733,7 @@
         for (int i = 0; i < numImes; ++i) {
             final InputMethodInfo imi = imes.get(i);
             final List<InputMethodSubtype> subtypes =
-                    mSettings.getEnabledInputMethodSubtypeList(imi, true);
+                    settings.getEnabledInputMethodSubtypeList(imi, true);
             final int subtypeCount = subtypes.size();
             if (subtypeCount == 0) {
                 ++nonAuxCount;
@@ -2835,12 +2844,16 @@
                     + " inv: " + (vis & InputMethodService.IME_INVISIBLE)
                     + " displayId: " + mCurTokenDisplayId);
         }
+        final IBinder focusedWindowToken = mImeBindingState != null
+                ? mImeBindingState.mFocusedWindow : null;
+        final Boolean windowPerceptible = focusedWindowToken != null
+                ? mFocusedWindowPerceptible.get(focusedWindowToken) : null;
 
         // TODO: Move this clearing calling identity block to setImeWindowStatus after making sure
         // all updateSystemUi happens on system privilege.
         final long ident = Binder.clearCallingIdentity();
         try {
-            if (!mCurPerceptible) {
+            if (windowPerceptible != null && !windowPerceptible) {
                 if ((vis & InputMethodService.IME_VISIBLE) != 0) {
                     vis &= ~InputMethodService.IME_VISIBLE;
                     vis |= InputMethodService.IME_VISIBLE_IMPERCEPTIBLE;
@@ -2872,11 +2885,12 @@
 
     @GuardedBy("ImfLock.class")
     void updateInputMethodsFromSettingsLocked(boolean enabledMayChange) {
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
         if (enabledMayChange) {
             final PackageManager userAwarePackageManager = getPackageManagerForUser(mContext,
-                    mSettings.getUserId());
+                    settings.getUserId());
 
-            List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodList();
+            List<InputMethodInfo> enabled = settings.getEnabledInputMethodList();
             for (int i = 0; i < enabled.size(); i++) {
                 // We allow the user to select "disabled until used" apps, so if they
                 // are enabling one of those here we now need to make it enabled.
@@ -2903,20 +2917,20 @@
 
         if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
             String ime = SecureSettingsWrapper.getString(
-                    Settings.Secure.DEFAULT_INPUT_METHOD, null, mSettings.getUserId());
+                    Settings.Secure.DEFAULT_INPUT_METHOD, null, settings.getUserId());
             String defaultDeviceIme = SecureSettingsWrapper.getString(
-                    Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, mSettings.getUserId());
+                    Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, settings.getUserId());
             if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
                 if (DEBUG) {
                     Slog.v(TAG, "Current input method " + ime + " differs from the stored default"
-                            + " device input method for user " + mSettings.getUserId()
+                            + " device input method for user " + settings.getUserId()
                             + " - restoring " + defaultDeviceIme);
                 }
                 SecureSettingsWrapper.putString(
                         Settings.Secure.DEFAULT_INPUT_METHOD, defaultDeviceIme,
-                        mSettings.getUserId());
+                        settings.getUserId());
                 SecureSettingsWrapper.putString(
-                        Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, mSettings.getUserId());
+                        Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, settings.getUserId());
             }
         }
 
@@ -2924,14 +2938,14 @@
         // ENABLED_INPUT_METHODS is taking care of keeping them correctly in
         // sync, so we will never have a DEFAULT_INPUT_METHOD that is not
         // enabled.
-        String id = mSettings.getSelectedInputMethod();
+        String id = settings.getSelectedInputMethod();
         // There is no input method selected, try to choose new applicable input method.
         if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) {
-            id = mSettings.getSelectedInputMethod();
+            id = settings.getSelectedInputMethod();
         }
         if (!TextUtils.isEmpty(id)) {
             try {
-                setInputMethodLocked(id, mSettings.getSelectedInputMethodSubtypeId(id));
+                setInputMethodLocked(id, settings.getSelectedInputMethodSubtypeId(id));
             } catch (IllegalArgumentException e) {
                 Slog.w(TAG, "Unknown input method from prefs: " + id, e);
                 resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_IME_FAILED);
@@ -2942,18 +2956,18 @@
         }
 
         // TODO: Instantiate mSwitchingController for each user.
-        if (mSettings.getUserId() == mSwitchingController.getUserId()) {
-            mSwitchingController.resetCircularListLocked(mSettings.getMethodMap());
+        if (settings.getUserId() == mSwitchingController.getUserId()) {
+            mSwitchingController.resetCircularListLocked(settings.getMethodMap());
         } else {
             mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
-                    mContext, mSettings.getMethodMap(), mSettings.getUserId());
+                    mContext, settings.getMethodMap(), settings.getUserId());
         }
         // TODO: Instantiate mHardwareKeyboardShortcutController for each user.
-        if (mSettings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) {
-            mHardwareKeyboardShortcutController.reset(mSettings.getMethodMap());
+        if (settings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) {
+            mHardwareKeyboardShortcutController.reset(settings.getMethodMap());
         } else {
             mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController(
-                    mSettings.getMethodMap(), mSettings.getUserId());
+                    settings.getMethodMap(), settings.getUserId());
         }
         sendOnNavButtonFlagsChangedLocked();
     }
@@ -2977,14 +2991,15 @@
 
     @GuardedBy("ImfLock.class")
     void setInputMethodLocked(String id, int subtypeId, int deviceId) {
-        InputMethodInfo info = mSettings.getMethodMap().get(id);
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+        InputMethodInfo info = settings.getMethodMap().get(id);
         if (info == null) {
             throw getExceptionForUnknownImeId(id);
         }
 
         // See if we need to notify a subtype change within the same IME.
         if (id.equals(getSelectedMethodIdLocked())) {
-            final int userId = mSettings.getUserId();
+            final int userId = settings.getUserId();
             final int subtypeCount = info.getSubtypeCount();
             if (subtypeCount <= 0) {
                 notifyInputMethodSubtypeChangedLocked(userId, info, null);
@@ -3025,7 +3040,7 @@
             // method is a custom one specific to a virtual device. So only update the settings
             // entry used to restore the default device input method once we want to show the IME
             // back on the default device.
-            mSettings.putSelectedDefaultDeviceInputMethod(id);
+            settings.putSelectedDefaultDeviceInputMethod(id);
             return;
         }
         IInputMethodInvoker curMethod = getCurMethodLocked();
@@ -3305,11 +3320,12 @@
         Binder.withCleanCallingIdentity(() -> {
             Objects.requireNonNull(windowToken, "windowToken must not be null");
             synchronized (ImfLock.class) {
+                Boolean windowPerceptible = mFocusedWindowPerceptible.get(windowToken);
                 if (mImeBindingState.mFocusedWindow != windowToken
-                        || mCurPerceptible == perceptible) {
+                        || (windowPerceptible != null && windowPerceptible == perceptible)) {
                     return;
                 }
-                mCurPerceptible = perceptible;
+                mFocusedWindowPerceptible.put(windowToken, windowPerceptible);
                 updateSystemUiLocked();
             }
         });
@@ -3535,7 +3551,7 @@
                             return InputBindResult.USER_SWITCHING;
                         }
                         final int[] profileIdsWithDisabled = mUserManagerInternal.getProfileIds(
-                                mSettings.getUserId(), false /* enabledOnly */);
+                                mCurrentUserId, false /* enabledOnly */);
                         for (int profileId : profileIdsWithDisabled) {
                             if (profileId == userId) {
                                 scheduleSwitchUserTaskLocked(userId, cs.mClient);
@@ -3581,10 +3597,10 @@
                     }
 
                     // Verify if caller is a background user.
-                    final int currentUserId = mSettings.getUserId();
-                    if (userId != currentUserId) {
+                    if (userId != mCurrentUserId) {
                         if (ArrayUtils.contains(
-                                mUserManagerInternal.getProfileIds(currentUserId, false), userId)) {
+                                mUserManagerInternal.getProfileIds(mCurrentUserId, false),
+                                userId)) {
                             // cross-profile access is always allowed here to allow
                             // profile-switching.
                             scheduleSwitchUserTaskLocked(userId, cs.mClient);
@@ -3687,7 +3703,7 @@
         }
 
         mImeBindingState = new ImeBindingState(windowToken, softInputMode, cs, editorInfo);
-        mCurPerceptible = true;
+        mFocusedWindowPerceptible.put(windowToken, true);
 
         // We want to start input before showing the IME, but after closing
         // it.  We want to do this after closing it to help the IME disappear
@@ -3773,7 +3789,7 @@
                 && mImeBindingState.mFocusedWindowClient.mClient.asBinder() == client.asBinder()) {
             return true;
         }
-        if (mSettings.getUserId() != UserHandle.getUserId(uid)) {
+        if (mCurrentUserId != UserHandle.getUserId(uid)) {
             return false;
         }
         if (getCurIntentLocked() != null && InputMethodUtils.checkIfPackageBelongsToUid(
@@ -3841,9 +3857,10 @@
             if (!calledWithValidTokenLocked(token)) {
                 return;
             }
-            final InputMethodInfo imi = mSettings.getMethodMap().get(id);
+            final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+            final InputMethodInfo imi = settings.getMethodMap().get(id);
             if (imi == null || !canCallerAccessInputMethod(
-                    imi.getPackageName(), callingUid, userId, mSettings)) {
+                    imi.getPackageName(), callingUid, userId, settings)) {
                 throw getExceptionForUnknownImeId(id);
             }
             setInputMethodWithSubtypeIdLocked(token, id, NOT_A_SUBTYPE_ID);
@@ -3859,9 +3876,10 @@
             if (!calledWithValidTokenLocked(token)) {
                 return;
             }
-            final InputMethodInfo imi = mSettings.getMethodMap().get(id);
+            final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+            final InputMethodInfo imi = settings.getMethodMap().get(id);
             if (imi == null || !canCallerAccessInputMethod(
-                    imi.getPackageName(), callingUid, userId, mSettings)) {
+                    imi.getPackageName(), callingUid, userId, settings)) {
                 throw getExceptionForUnknownImeId(id);
             }
             if (subtype != null) {
@@ -3879,10 +3897,11 @@
             if (!calledWithValidTokenLocked(token)) {
                 return false;
             }
-            final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtype();
+            final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+            final Pair<String, String> lastIme = settings.getLastInputMethodAndSubtype();
             final InputMethodInfo lastImi;
             if (lastIme != null) {
-                lastImi = mSettings.getMethodMap().get(lastIme.first);
+                lastImi = settings.getMethodMap().get(lastIme.first);
             } else {
                 lastImi = null;
             }
@@ -3906,7 +3925,7 @@
                 // This is a safety net. If the currentSubtype can't be added to the history
                 // and the framework couldn't find the last ime, we will make the last ime be
                 // the most applicable enabled keyboard subtype of the system imes.
-                final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodList();
+                final List<InputMethodInfo> enabled = settings.getEnabledInputMethodList();
                 if (enabled != null) {
                     final int enabledCount = enabled.size();
                     final String locale;
@@ -3914,7 +3933,7 @@
                             && !TextUtils.isEmpty(mCurrentSubtype.getLocale())) {
                         locale = mCurrentSubtype.getLocale();
                     } else {
-                        locale = SystemLocaleWrapper.get(mSettings.getUserId()).get(0).toString();
+                        locale = SystemLocaleWrapper.get(mCurrentUserId).get(0).toString();
                     }
                     for (int i = 0; i < enabledCount; ++i) {
                         final InputMethodInfo imi = enabled.get(i);
@@ -3961,8 +3980,9 @@
 
     @GuardedBy("ImfLock.class")
     private boolean switchToNextInputMethodLocked(@Nullable IBinder token, boolean onlyCurrentIme) {
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
         final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
-                onlyCurrentIme, mSettings.getMethodMap().get(getSelectedMethodIdLocked()),
+                onlyCurrentIme, settings.getMethodMap().get(getSelectedMethodIdLocked()),
                 mCurrentSubtype);
         if (nextSubtype == null) {
             return false;
@@ -3978,9 +3998,10 @@
             if (!calledWithValidTokenLocked(token)) {
                 return false;
             }
+            final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
             final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
                     false /* onlyCurrentIme */,
-                    mSettings.getMethodMap().get(getSelectedMethodIdLocked()), mCurrentSubtype);
+                    settings.getMethodMap().get(getSelectedMethodIdLocked()), mCurrentSubtype);
             return nextSubtype != null;
         }
     }
@@ -3992,12 +4013,7 @@
                     Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
         }
         synchronized (ImfLock.class) {
-            if (mSettings.getUserId() == userId) {
-                return mSettings.getLastInputMethodSubtype();
-            }
-
-            final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
-            return settings.getLastInputMethodSubtype();
+            return InputMethodSettingsRepository.get(userId).getLastInputMethodSubtype();
         }
     }
 
@@ -4028,20 +4044,21 @@
             }
 
             final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
-            final boolean isCurrentUser = (mSettings.getUserId() == userId);
-            final InputMethodSettings settings = isCurrentUser
-                    ? mSettings
-                    : queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
-                            DirectBootAwareness.AUTO);
+            final boolean isCurrentUser = (mCurrentUserId == userId);
+            final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
             final var newAdditionalSubtypeMap = settings.getNewAdditionalSubtypeMap(
                     imiId, toBeAdded, additionalSubtypeMap, mPackageManagerInternal, callingUid);
             if (additionalSubtypeMap != newAdditionalSubtypeMap) {
                 AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
                         settings.getMethodMap());
+                final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+                        userId, AdditionalSubtypeMapRepository.get(userId),
+                        DirectBootAwareness.AUTO);
+                InputMethodSettingsRepository.put(userId, newSettings);
                 if (isCurrentUser) {
                     final long ident = Binder.clearCallingIdentity();
                     try {
-                        buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
+                        postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
                     } finally {
                         Binder.restoreCallingIdentity(ident);
                     }
@@ -4070,9 +4087,8 @@
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (ImfLock.class) {
-                final boolean currentUser = (mSettings.getUserId() == userId);
-                final InputMethodSettings settings = currentUser
-                        ? mSettings : queryMethodMapForUserLocked(userId);
+                final boolean currentUser = (mCurrentUserId == userId);
+                final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
                 if (!settings.setEnabledInputMethodSubtypes(imeId, subtypeHashCodes)) {
                     return;
                 }
@@ -4422,11 +4438,11 @@
                 }
                 return;
             }
-            if (mSettings.getUserId() != mSwitchingController.getUserId()) {
+            if (mCurrentUserId != mSwitchingController.getUserId()) {
                 return;
             }
-            final InputMethodInfo imi =
-                    mSettings.getMethodMap().get(getSelectedMethodIdLocked());
+            final InputMethodInfo imi = InputMethodSettingsRepository.get(mCurrentUserId)
+                    .getMethodMap().get(getSelectedMethodIdLocked());
             if (imi != null) {
                 mSwitchingController.onUserActionLocked(imi, mCurrentSubtype);
             }
@@ -4486,8 +4502,9 @@
             return;
         } else {
             // Called with current IME's token.
-            if (mSettings.getMethodMap().get(id) != null
-                    && mSettings.getEnabledInputMethodListWithFilter(
+            final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+            if (settings.getMethodMap().get(id) != null
+                    && settings.getEnabledInputMethodListWithFilter(
                             (info) -> info.getId().equals(id)).isEmpty()) {
                 throw new IllegalStateException("Requested IME is not enabled: " + id);
             }
@@ -4666,21 +4683,23 @@
                         return false;
                 }
                 synchronized (ImfLock.class) {
+                    final InputMethodSettings settings =
+                            InputMethodSettingsRepository.get(mCurrentUserId);
                     final boolean isScreenLocked = mWindowManagerInternal.isKeyguardLocked()
-                            && mWindowManagerInternal.isKeyguardSecure(mSettings.getUserId());
-                    final String lastInputMethodId = mSettings.getSelectedInputMethod();
+                            && mWindowManagerInternal.isKeyguardSecure(settings.getUserId());
+                    final String lastInputMethodId = settings.getSelectedInputMethod();
                     int lastInputMethodSubtypeId =
-                            mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId);
+                            settings.getSelectedInputMethodSubtypeId(lastInputMethodId);
 
                     final List<ImeSubtypeListItem> imList = InputMethodSubtypeSwitchingController
                             .getSortedInputMethodAndSubtypeList(
                                     showAuxSubtypes, isScreenLocked, true /* forImeMenu */,
-                                    mContext, mSettings.getMethodMap(), mSettings.getUserId());
+                                    mContext, settings.getMethodMap(), settings.getUserId());
                     if (imList.isEmpty()) {
                         Slog.w(TAG, "Show switching menu failed, imList is empty,"
                                 + " showAuxSubtypes: " + showAuxSubtypes
                                 + " isScreenLocked: " + isScreenLocked
-                                + " userId: " + mSettings.getUserId());
+                                + " userId: " + settings.getUserId());
                         return false;
                     }
 
@@ -4866,8 +4885,9 @@
 
     @GuardedBy("ImfLock.class")
     private boolean chooseNewDefaultIMELocked() {
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
         final InputMethodInfo imi = InputMethodInfoUtils.getMostApplicableDefaultIME(
-                mSettings.getEnabledInputMethodList());
+                settings.getEnabledInputMethodList());
         if (imi != null) {
             if (DEBUG) {
                 Slog.d(TAG, "New default IME was selected: " + imi.getId());
@@ -4969,7 +4989,7 @@
     }
 
     @GuardedBy("ImfLock.class")
-    void buildInputMethodListLocked(boolean resetDefaultEnabledIme) {
+    void postInputMethodSettingUpdatedLocked(boolean resetDefaultEnabledIme) {
         if (DEBUG) {
             Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme
                     + " \n ------ caller=" + Debug.getCallers(10));
@@ -4981,9 +5001,7 @@
         mMethodMapUpdateCount++;
         mMyPackageMonitor.clearKnownImePackageNamesLocked();
 
-        mSettings = queryInputMethodServicesInternal(mContext, mSettings.getUserId(),
-                AdditionalSubtypeMapRepository.get(mSettings.getUserId()),
-                DirectBootAwareness.AUTO);
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
 
         // Construct the set of possible IME packages for onPackageChanged() to avoid false
         // negatives when the package state remains to be the same but only the component state is
@@ -4995,7 +5013,7 @@
             final List<ResolveInfo> allInputMethodServices =
                     mContext.getPackageManager().queryIntentServicesAsUser(
                             new Intent(InputMethod.SERVICE_INTERFACE),
-                            PackageManager.MATCH_DISABLED_COMPONENTS, mSettings.getUserId());
+                            PackageManager.MATCH_DISABLED_COMPONENTS, settings.getUserId());
             final int numImes = allInputMethodServices.size();
             for (int i = 0; i < numImes; ++i) {
                 final ServiceInfo si = allInputMethodServices.get(i).serviceInfo;
@@ -5010,11 +5028,11 @@
         if (!resetDefaultEnabledIme) {
             boolean enabledImeFound = false;
             boolean enabledNonAuxImeFound = false;
-            final List<InputMethodInfo> enabledImes = mSettings.getEnabledInputMethodList();
+            final List<InputMethodInfo> enabledImes = settings.getEnabledInputMethodList();
             final int numImes = enabledImes.size();
             for (int i = 0; i < numImes; ++i) {
                 final InputMethodInfo imi = enabledImes.get(i);
-                if (mSettings.getMethodMap().containsKey(imi.getId())) {
+                if (settings.getMethodMap().containsKey(imi.getId())) {
                     enabledImeFound = true;
                     if (!imi.isAuxiliaryIme()) {
                         enabledNonAuxImeFound = true;
@@ -5038,7 +5056,7 @@
 
         if (resetDefaultEnabledIme || reenableMinimumNonAuxSystemImes) {
             final ArrayList<InputMethodInfo> defaultEnabledIme =
-                    InputMethodInfoUtils.getDefaultEnabledImes(mContext, mSettings.getMethodList(),
+                    InputMethodInfoUtils.getDefaultEnabledImes(mContext, settings.getMethodList(),
                             reenableMinimumNonAuxSystemImes);
             final int numImes = defaultEnabledIme.size();
             for (int i = 0; i < numImes; ++i) {
@@ -5050,9 +5068,9 @@
             }
         }
 
-        final String defaultImiId = mSettings.getSelectedInputMethod();
+        final String defaultImiId = settings.getSelectedInputMethod();
         if (!TextUtils.isEmpty(defaultImiId)) {
-            if (!mSettings.getMethodMap().containsKey(defaultImiId)) {
+            if (!settings.getMethodMap().containsKey(defaultImiId)) {
                 Slog.w(TAG, "Default IME is uninstalled. Choose new default IME.");
                 if (chooseNewDefaultIMELocked()) {
                     updateInputMethodsFromSettingsLocked(true);
@@ -5066,26 +5084,26 @@
         updateDefaultVoiceImeIfNeededLocked();
 
         // TODO: Instantiate mSwitchingController for each user.
-        if (mSettings.getUserId() == mSwitchingController.getUserId()) {
-            mSwitchingController.resetCircularListLocked(mSettings.getMethodMap());
+        if (settings.getUserId() == mSwitchingController.getUserId()) {
+            mSwitchingController.resetCircularListLocked(settings.getMethodMap());
         } else {
             mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
-                    mContext, mSettings.getMethodMap(), mSettings.getUserId());
+                    mContext, settings.getMethodMap(), mCurrentUserId);
         }
         // TODO: Instantiate mHardwareKeyboardShortcutController for each user.
-        if (mSettings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) {
-            mHardwareKeyboardShortcutController.reset(mSettings.getMethodMap());
+        if (settings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) {
+            mHardwareKeyboardShortcutController.reset(settings.getMethodMap());
         } else {
             mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController(
-                    mSettings.getMethodMap(), mSettings.getUserId());
+                    settings.getMethodMap(), settings.getUserId());
         }
 
         sendOnNavButtonFlagsChangedLocked();
 
         // Notify InputMethodListListeners of the new installed InputMethods.
-        final List<InputMethodInfo> inputMethodList = mSettings.getMethodList();
+        final List<InputMethodInfo> inputMethodList = settings.getMethodList();
         mHandler.obtainMessage(MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED,
-                mSettings.getUserId(), 0 /* unused */, inputMethodList).sendToTarget();
+                settings.getUserId(), 0 /* unused */, inputMethodList).sendToTarget();
     }
 
     @GuardedBy("ImfLock.class")
@@ -5100,11 +5118,12 @@
 
     @GuardedBy("ImfLock.class")
     private void updateDefaultVoiceImeIfNeededLocked() {
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
         final String systemSpeechRecognizer =
                 mContext.getString(com.android.internal.R.string.config_systemSpeechRecognizer);
-        final String currentDefaultVoiceImeId = mSettings.getDefaultVoiceInputMethod();
+        final String currentDefaultVoiceImeId = settings.getDefaultVoiceInputMethod();
         final InputMethodInfo newSystemVoiceIme = InputMethodInfoUtils.chooseSystemVoiceIme(
-                mSettings.getMethodMap(), systemSpeechRecognizer, currentDefaultVoiceImeId);
+                settings.getMethodMap(), systemSpeechRecognizer, currentDefaultVoiceImeId);
         if (newSystemVoiceIme == null) {
             if (DEBUG) {
                 Slog.i(TAG, "Found no valid default Voice IME. If the user is still locked,"
@@ -5113,7 +5132,7 @@
             // Clear DEFAULT_VOICE_INPUT_METHOD when necessary.  Note that InputMethodSettings
             // does not update the actual Secure Settings until the user is unlocked.
             if (!TextUtils.isEmpty(currentDefaultVoiceImeId)) {
-                mSettings.putDefaultVoiceInputMethod("");
+                settings.putDefaultVoiceInputMethod("");
                 // We don't support disabling the voice ime when a package is removed from the
                 // config.
             }
@@ -5126,7 +5145,7 @@
             Slog.i(TAG, "Enabling the default Voice IME:" + newSystemVoiceIme);
         }
         setInputMethodEnabledLocked(newSystemVoiceIme.getId(), true);
-        mSettings.putDefaultVoiceInputMethod(newSystemVoiceIme.getId());
+        settings.putDefaultVoiceInputMethod(newSystemVoiceIme.getId());
     }
 
     // ----------------------------------------------------------------------
@@ -5141,8 +5160,9 @@
      */
     @GuardedBy("ImfLock.class")
     private boolean setInputMethodEnabledLocked(String id, boolean enabled) {
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
         if (enabled) {
-            final String enabledImeIdsStr = mSettings.getEnabledInputMethodsStr();
+            final String enabledImeIdsStr = settings.getEnabledInputMethodsStr();
             final String newEnabledImeIdsStr = InputMethodUtils.concatEnabledImeIds(
                     enabledImeIdsStr, id);
             if (TextUtils.equals(enabledImeIdsStr, newEnabledImeIdsStr)) {
@@ -5150,29 +5170,29 @@
                 // Nothing to do. The previous state was enabled.
                 return true;
             }
-            mSettings.putEnabledInputMethodsStr(newEnabledImeIdsStr);
+            settings.putEnabledInputMethodsStr(newEnabledImeIdsStr);
             // Previous state was disabled.
             return false;
         } else {
-            final List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
+            final List<Pair<String, ArrayList<String>>> enabledInputMethodsList = settings
                     .getEnabledInputMethodsAndSubtypeList();
             StringBuilder builder = new StringBuilder();
-            if (mSettings.buildAndPutEnabledInputMethodsStrRemovingId(
+            if (settings.buildAndPutEnabledInputMethodsStrRemovingId(
                     builder, enabledInputMethodsList, id)) {
                 if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
                     // Disabled input method is currently selected, switch to another one.
-                    final String selId = mSettings.getSelectedInputMethod();
+                    final String selId = settings.getSelectedInputMethod();
                     if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
                         Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
                         resetSelectedInputMethodAndSubtypeLocked("");
                     }
-                } else if (id.equals(mSettings.getSelectedDefaultDeviceInputMethod())) {
+                } else if (id.equals(settings.getSelectedDefaultDeviceInputMethod())) {
                     // Disabled default device IME while using a virtual device one, choose a
                     // new default one but only update the settings.
                     InputMethodInfo newDefaultIme =
                             InputMethodInfoUtils.getMostApplicableDefaultIME(
-                                        mSettings.getEnabledInputMethodList());
-                    mSettings.putSelectedDefaultDeviceInputMethod(
+                                    settings.getEnabledInputMethodList());
+                    settings.putSelectedDefaultDeviceInputMethod(
                             newDefaultIme == null ? null : newDefaultIme.getId());
                 }
                 // Previous state was enabled.
@@ -5188,29 +5208,30 @@
     @GuardedBy("ImfLock.class")
     private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
             boolean setSubtypeOnly) {
-        mSettings.saveCurrentInputMethodAndSubtypeToHistory(getSelectedMethodIdLocked(),
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+        settings.saveCurrentInputMethodAndSubtypeToHistory(getSelectedMethodIdLocked(),
                 mCurrentSubtype);
 
         // Set Subtype here
         if (imi == null || subtypeId < 0) {
-            mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
+            settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
             mCurrentSubtype = null;
         } else {
             if (subtypeId < imi.getSubtypeCount()) {
                 InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId);
-                mSettings.putSelectedSubtype(subtype.hashCode());
+                settings.putSelectedSubtype(subtype.hashCode());
                 mCurrentSubtype = subtype;
             } else {
-                mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
+                settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
                 // If the subtype is not specified, choose the most applicable one
                 mCurrentSubtype = getCurrentInputMethodSubtypeLocked();
             }
         }
-        notifyInputMethodSubtypeChangedLocked(mSettings.getUserId(), imi, mCurrentSubtype);
+        notifyInputMethodSubtypeChangedLocked(settings.getUserId(), imi, mCurrentSubtype);
 
         if (!setSubtypeOnly) {
             // Set InputMethod here
-            mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
+            settings.putSelectedInputMethod(imi != null ? imi.getId() : "");
         }
     }
 
@@ -5218,13 +5239,15 @@
     private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) {
         mDeviceIdToShowIme = DEVICE_ID_DEFAULT;
         mDisplayIdToShowIme = INVALID_DISPLAY;
-        mSettings.putSelectedDefaultDeviceInputMethod(null);
 
-        InputMethodInfo imi = mSettings.getMethodMap().get(newDefaultIme);
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+        settings.putSelectedDefaultDeviceInputMethod(null);
+
+        InputMethodInfo imi = settings.getMethodMap().get(newDefaultIme);
         int lastSubtypeId = NOT_A_SUBTYPE_ID;
         // newDefaultIme is empty when there is no candidate for the selected IME.
         if (imi != null && !TextUtils.isEmpty(newDefaultIme)) {
-            String subtypeHashCode = mSettings.getLastSubtypeForInputMethod(newDefaultIme);
+            String subtypeHashCode = settings.getLastSubtypeForInputMethod(newDefaultIme);
             if (subtypeHashCode != null) {
                 try {
                     lastSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(imi,
@@ -5251,12 +5274,12 @@
                     Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
         }
         synchronized (ImfLock.class) {
-            if (mSettings.getUserId() == userId) {
+            if (mCurrentUserId == userId) {
                 return getCurrentInputMethodSubtypeLocked();
             }
 
-            final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
-            return settings.getCurrentInputMethodSubtypeForNonCurrentUsers();
+            return InputMethodSettingsRepository.get(userId)
+                    .getCurrentInputMethodSubtypeForNonCurrentUsers();
         }
     }
 
@@ -5276,26 +5299,27 @@
         if (selectedMethodId == null) {
             return null;
         }
-        final boolean subtypeIsSelected = mSettings.isSubtypeSelected();
-        final InputMethodInfo imi = mSettings.getMethodMap().get(selectedMethodId);
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+        final boolean subtypeIsSelected = settings.isSubtypeSelected();
+        final InputMethodInfo imi = settings.getMethodMap().get(selectedMethodId);
         if (imi == null || imi.getSubtypeCount() == 0) {
             return null;
         }
         if (!subtypeIsSelected || mCurrentSubtype == null
                 || !SubtypeUtils.isValidSubtypeId(imi, mCurrentSubtype.hashCode())) {
-            int subtypeId = mSettings.getSelectedInputMethodSubtypeId(selectedMethodId);
+            int subtypeId = settings.getSelectedInputMethodSubtypeId(selectedMethodId);
             if (subtypeId == NOT_A_SUBTYPE_ID) {
                 // If there are no selected subtypes, the framework will try to find
                 // the most applicable subtype from explicitly or implicitly enabled
                 // subtypes.
                 List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
-                        mSettings.getEnabledInputMethodSubtypeList(imi, true);
+                        settings.getEnabledInputMethodSubtypeList(imi, true);
                 // If there is only one explicitly or implicitly enabled subtype,
                 // just returns it.
                 if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
                     mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
                 } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
-                    final String locale = SystemLocaleWrapper.get(mSettings.getUserId())
+                    final String locale = SystemLocaleWrapper.get(settings.getUserId())
                             .get(0).toString();
                     mCurrentSubtype = SubtypeUtils.findLastResortApplicableSubtype(
                             explicitlyOrImplicitlyEnabledSubtypes,
@@ -5318,38 +5342,22 @@
      */
     @GuardedBy("ImfLock.class")
     private InputMethodInfo queryDefaultInputMethodForUserIdLocked(@UserIdInt int userId) {
-        final InputMethodSettings settings;
-        if (userId == mSettings.getUserId()) {
-            settings = mSettings;
-        } else {
-            final AdditionalSubtypeMap additionalSubtypeMap =
-                    AdditionalSubtypeMapRepository.get(userId);
-            settings = queryInputMethodServicesInternal(mContext, userId,
-                    additionalSubtypeMap, DirectBootAwareness.AUTO);
-        }
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
         return settings.getMethodMap().get(settings.getSelectedInputMethod());
     }
 
     @GuardedBy("ImfLock.class")
-    private InputMethodSettings queryMethodMapForUserLocked(@UserIdInt int userId) {
-        final AdditionalSubtypeMap additionalSubtypeMap =
-                AdditionalSubtypeMapRepository.get(userId);
-        return queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
-                DirectBootAwareness.AUTO);
-    }
-
-    @GuardedBy("ImfLock.class")
     private boolean switchToInputMethodLocked(String imeId, @UserIdInt int userId) {
-        if (userId == mSettings.getUserId()) {
-            if (!mSettings.getMethodMap().containsKey(imeId)
-                    || !mSettings.getEnabledInputMethodList()
-                    .contains(mSettings.getMethodMap().get(imeId))) {
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+        if (userId == mCurrentUserId) {
+            if (!settings.getMethodMap().containsKey(imeId)
+                    || !settings.getEnabledInputMethodList()
+                    .contains(settings.getMethodMap().get(imeId))) {
                 return false; // IME is not found or not enabled.
             }
             setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID);
             return true;
         }
-        final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
         if (!settings.getMethodMap().containsKey(imeId)
                 || !settings.getEnabledInputMethodList().contains(
                         settings.getMethodMap().get(imeId))) {
@@ -5390,8 +5398,9 @@
 
     @GuardedBy("ImfLock.class")
     private void switchKeyboardLayoutLocked(int direction) {
-        final InputMethodInfo currentImi = mSettings.getMethodMap().get(
-                getSelectedMethodIdLocked());
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+
+        final InputMethodInfo currentImi = settings.getMethodMap().get(getSelectedMethodIdLocked());
         if (currentImi == null) {
             return;
         }
@@ -5403,7 +5412,7 @@
         if (nextSubtypeHandle == null) {
             return;
         }
-        final InputMethodInfo nextImi = mSettings.getMethodMap().get(nextSubtypeHandle.getImeId());
+        final InputMethodInfo nextImi = settings.getMethodMap().get(nextSubtypeHandle.getImeId());
         if (nextImi == null) {
             return;
         }
@@ -5482,17 +5491,14 @@
         @Override
         public boolean setInputMethodEnabled(String imeId, boolean enabled, @UserIdInt int userId) {
             synchronized (ImfLock.class) {
-                if (userId == mSettings.getUserId()) {
-                    if (!mSettings.getMethodMap().containsKey(imeId)) {
-                        return false; // IME is not found.
-                    }
-                    setInputMethodEnabledLocked(imeId, enabled);
-                    return true;
-                }
-                final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+                final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
                 if (!settings.getMethodMap().containsKey(imeId)) {
                     return false; // IME is not found.
                 }
+                if (userId == mCurrentUserId) {
+                    setInputMethodEnabledLocked(imeId, enabled);
+                    return true;
+                }
                 if (enabled) {
                     final String enabledImeIdsStr = settings.getEnabledInputMethodsStr();
                     final String newEnabledImeIdsStr = InputMethodUtils.concatEnabledImeIds(
@@ -5548,9 +5554,11 @@
         public void reportImeControl(@Nullable IBinder windowToken) {
             synchronized (ImfLock.class) {
                 if (mImeBindingState.mFocusedWindow != windowToken) {
-                    // mCurPerceptible was set by the focused window, but it is no longer in
-                    // control, so we reset mCurPerceptible.
-                    mCurPerceptible = true;
+                    // A perceptible value was set for the focused window, but it is no longer in
+                    // control, so we reset the perceptible for the window passed as argument.
+                    // TODO(b/314149476): Investigate whether this logic is still relevant, if not
+                    //     then consider removing using concurrent_input_methods feature flag.
+                    mFocusedWindowPerceptible.put(windowToken, true);
                 }
             }
         }
@@ -5825,8 +5833,9 @@
         final Printer p = new PrintWriterPrinter(pw);
 
         synchronized (ImfLock.class) {
+            final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
             p.println("Current Input Method Manager state:");
-            final List<InputMethodInfo> methodList = mSettings.getMethodList();
+            final List<InputMethodInfo> methodList = settings.getMethodList();
             int numImes = methodList.size();
             p.println("  Input Methods: mMethodMapUpdateCount=" + mMethodMapUpdateCount);
             for (int i = 0; i < numImes; i++) {
@@ -5850,11 +5859,10 @@
                 p.println("    curSession=" + c.mCurSession);
             };
             mClientController.forAllClients(clientControllerDump);
-
             p.println("  mCurMethodId=" + getSelectedMethodIdLocked());
             client = mCurClient;
             p.println("  mCurClient=" + client + " mCurSeq=" + getSequenceNumberLocked());
-            p.println("  mCurPerceptible=" + mCurPerceptible);
+            p.println("  mFocusedWindowPerceptible=" + mFocusedWindowPerceptible);
             mImeBindingState.dump("  ", p);
             p.println("  mCurId=" + getCurIdLocked() + " mHaveConnection=" + hasConnectionLocked()
                     + " mBoundToMethod=" + mBoundToMethod + " mVisibleBound="
@@ -5877,7 +5885,7 @@
             p.println("  mSwitchingController:");
             mSwitchingController.dump(p);
             p.println("  mSettings:");
-            mSettings.dump(p, "    ");
+            settings.dump(p, "    ");
 
             p.println("  mStartInputHistory:");
             mStartInputHistory.dump(pw, "    ");
@@ -6132,7 +6140,7 @@
         }
         synchronized (ImfLock.class) {
             final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
-                    mSettings.getUserId(), shellCommand.getErrPrintWriter());
+                    mCurrentUserId, shellCommand.getErrPrintWriter());
             try (PrintWriter pr = shellCommand.getOutPrintWriter()) {
                 for (int userId : userIds) {
                     final List<InputMethodInfo> methods = all
@@ -6177,7 +6185,7 @@
              PrintWriter error = shellCommand.getErrPrintWriter()) {
             synchronized (ImfLock.class) {
                 final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
-                        mSettings.getUserId(), shellCommand.getErrPrintWriter());
+                        mCurrentUserId, shellCommand.getErrPrintWriter());
                 for (int userId : userIds) {
                     if (!userHasDebugPriv(userId, shellCommand)) {
                         continue;
@@ -6236,14 +6244,14 @@
             PrintWriter error) {
         boolean failedToEnableUnknownIme = false;
         boolean previouslyEnabled = false;
-        if (userId == mSettings.getUserId()) {
-            if (enabled && !mSettings.getMethodMap().containsKey(imeId)) {
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+        if (userId == mCurrentUserId) {
+            if (enabled && !settings.getMethodMap().containsKey(imeId)) {
                 failedToEnableUnknownIme = true;
             } else {
                 previouslyEnabled = setInputMethodEnabledLocked(imeId, enabled);
             }
         } else {
-            final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
             if (enabled) {
                 if (!settings.getMethodMap().containsKey(imeId)) {
                     failedToEnableUnknownIme = true;
@@ -6298,7 +6306,7 @@
              PrintWriter error = shellCommand.getErrPrintWriter()) {
             synchronized (ImfLock.class) {
                 final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
-                        mSettings.getUserId(), shellCommand.getErrPrintWriter());
+                        mCurrentUserId, shellCommand.getErrPrintWriter());
                 for (int userId : userIds) {
                     if (!userHasDebugPriv(userId, shellCommand)) {
                         continue;
@@ -6338,7 +6346,7 @@
         synchronized (ImfLock.class) {
             try (PrintWriter out = shellCommand.getOutPrintWriter()) {
                 final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
-                        mSettings.getUserId(), shellCommand.getErrPrintWriter());
+                        mCurrentUserId, shellCommand.getErrPrintWriter());
                 for (int userId : userIds) {
                     if (!userHasDebugPriv(userId, shellCommand)) {
                         continue;
@@ -6350,15 +6358,16 @@
                     }
                     final String nextIme;
                     final List<InputMethodInfo> nextEnabledImes;
-                    if (userId == mSettings.getUserId()) {
+                    final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+                    if (userId == mCurrentUserId) {
                         hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
                                 SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND);
                         mBindingController.unbindCurrentMethod();
 
                         // Enable default IMEs, disable others
-                        var toDisable = mSettings.getEnabledInputMethodList();
+                        var toDisable = settings.getEnabledInputMethodList();
                         var defaultEnabled = InputMethodInfoUtils.getDefaultEnabledImes(
-                                mContext, mSettings.getMethodList());
+                                mContext, settings.getMethodList());
                         toDisable.removeAll(defaultEnabled);
                         for (InputMethodInfo info : toDisable) {
                             setInputMethodEnabledLocked(info.getId(), false);
@@ -6372,16 +6381,11 @@
                         }
                         updateInputMethodsFromSettingsLocked(true /* enabledMayChange */);
                         InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
-                                getPackageManagerForUser(mContext, mSettings.getUserId()),
-                                mSettings.getEnabledInputMethodList());
-                        nextIme = mSettings.getSelectedInputMethod();
-                        nextEnabledImes = mSettings.getEnabledInputMethodList();
+                                getPackageManagerForUser(mContext, settings.getUserId()),
+                                settings.getEnabledInputMethodList());
+                        nextIme = settings.getSelectedInputMethod();
+                        nextEnabledImes = settings.getEnabledInputMethodList();
                     } else {
-                        final AdditionalSubtypeMap additionalSubtypeMap =
-                                AdditionalSubtypeMapRepository.get(userId);
-                        final InputMethodSettings settings = queryInputMethodServicesInternal(
-                                mContext, userId, additionalSubtypeMap, DirectBootAwareness.AUTO);
-
                         nextEnabledImes = InputMethodInfoUtils.getDefaultEnabledImes(mContext,
                                 settings.getMethodList());
                         nextIme = InputMethodInfoUtils.getMostApplicableDefaultIME(
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
new file mode 100644
index 0000000..60b9a4c
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.Handler;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.DirectBootAwareness;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+final class InputMethodSettingsRepository {
+    @GuardedBy("ImfLock.class")
+    @NonNull
+    private static final SparseArray<InputMethodSettings> sPerUserMap = new SparseArray<>();
+
+    /**
+     * Not intended to be instantiated.
+     */
+    private InputMethodSettingsRepository() {
+    }
+
+    @NonNull
+    @GuardedBy("ImfLock.class")
+    static InputMethodSettings get(@UserIdInt int userId) {
+        final InputMethodSettings obj = sPerUserMap.get(userId);
+        if (obj != null) {
+            return obj;
+        }
+        return InputMethodSettings.createEmptyMap(userId);
+    }
+
+    @GuardedBy("ImfLock.class")
+    static void put(@UserIdInt int userId, @NonNull InputMethodSettings obj) {
+        sPerUserMap.put(userId, obj);
+    }
+
+    static void initialize(@NonNull Handler handler, @NonNull Context context) {
+        final UserManagerInternal userManagerInternal =
+                LocalServices.getService(UserManagerInternal.class);
+        handler.post(() -> {
+            userManagerInternal.addUserLifecycleListener(
+                    new UserManagerInternal.UserLifecycleListener() {
+                        @Override
+                        public void onUserRemoved(UserInfo user) {
+                            final int userId = user.id;
+                            handler.post(() -> {
+                                synchronized (ImfLock.class) {
+                                    sPerUserMap.remove(userId);
+                                }
+                            });
+                        }
+                    });
+            synchronized (ImfLock.class) {
+                for (int userId : userManagerInternal.getUserIds()) {
+                    final InputMethodSettings settings =
+                            InputMethodManagerService.queryInputMethodServicesInternal(
+                                    context,
+                                    userId,
+                                    AdditionalSubtypeMapRepository.get(userId),
+                                    DirectBootAwareness.AUTO);
+                    sPerUserMap.put(userId, settings);
+                }
+            }
+        });
+    }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c38fbda..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);
     }
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/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 4bfd077..4bec61a 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2892,17 +2892,20 @@
                 mPm.notifyPackageChanged(packageName, request.getAppId());
             }
 
-            // Apply restricted settings on potentially dangerous packages. Needs to happen
-            // after appOpsManager is notified of the new package
-            if (request.getPackageSource() == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE
-                    || request.getPackageSource()
-                    == PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE) {
-                final int appId = request.getAppId();
-                mPm.mHandler.post(() -> {
-                    for (int userId : firstUserIds) {
-                        enableRestrictedSettings(packageName, appId, userId);
-                    }
-                });
+            if (!android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()
+                    || !android.security.Flags.extendEcmToAllSettings()) {
+                // Apply restricted settings on potentially dangerous packages. Needs to happen
+                // after appOpsManager is notified of the new package
+                if (request.getPackageSource() == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE
+                        || request.getPackageSource()
+                        == PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE) {
+                    final int appId = request.getAppId();
+                    mPm.mHandler.post(() -> {
+                        for (int userId : firstUserIds) {
+                            enableRestrictedSettings(packageName, appId, userId);
+                        }
+                    });
+                }
             }
 
             // Log current value of "unknown sources" setting
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index ba90378..3eeeae7 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -148,6 +148,7 @@
 import android.os.storage.StorageManager;
 import android.provider.DeviceConfig;
 import android.provider.Settings.Global;
+import android.service.persistentdata.PersistentDataBlockManager;
 import android.stats.devicepolicy.DevicePolicyEnums;
 import android.system.ErrnoException;
 import android.system.Int64Ref;
@@ -964,13 +965,22 @@
 
     private boolean isEmergencyInstallerEnabled(String packageName, Computer snapshot) {
         final PackageStateInternal ps = snapshot.getPackageStateInternal(packageName);
-        if (ps == null || ps.getPkg() == null) {
+        if (ps == null || ps.getPkg() == null || !ps.isSystem()) {
             return false;
         }
+        int uid = UserHandle.getUid(userId, ps.getAppId());
         String emergencyInstaller = ps.getPkg().getEmergencyInstaller();
         if (emergencyInstaller == null || !ArrayUtils.contains(
-                snapshot.getPackagesForUid(mInstallerUid),
-                emergencyInstaller)) {
+                snapshot.getPackagesForUid(mInstallerUid), emergencyInstaller)) {
+            return false;
+        }
+        // Only system installers can have an emergency installer
+        if (PackageManager.PERMISSION_GRANTED
+                != snapshot.checkUidPermission(Manifest.permission.INSTALL_PACKAGES, uid)
+                && PackageManager.PERMISSION_GRANTED
+                != snapshot.checkUidPermission(Manifest.permission.INSTALL_PACKAGE_UPDATES, uid)
+                && PackageManager.PERMISSION_GRANTED
+                != snapshot.checkUidPermission(Manifest.permission.INSTALL_SELF_UPDATES, uid)) {
             return false;
         }
         return (snapshot.checkUidPermission(Manifest.permission.EMERGENCY_INSTALL_PACKAGES,
@@ -2355,8 +2365,21 @@
             assertPreparedAndNotDestroyedLocked("commit of session " + sessionId);
             assertNoWriteFileTransfersOpenLocked();
 
-            final boolean isSecureFrpEnabled =
-                    Global.getInt(mContext.getContentResolver(), Global.SECURE_FRP_MODE, 0) == 1;
+            boolean isSecureFrpEnabled;
+            if (android.security.Flags.frpEnforcement()) {
+                PersistentDataBlockManager pdbManager =
+                        mContext.getSystemService(PersistentDataBlockManager.class);
+                if (pdbManager == null) {
+                    // Some devices may not support FRP. In that case, we can't block the install
+                    // accordingly.
+                    isSecureFrpEnabled = false;
+                } else {
+                    isSecureFrpEnabled = pdbManager.isFactoryResetProtectionActive();
+                }
+            } else {
+                isSecureFrpEnabled = Global.getInt(mContext.getContentResolver(),
+                        Global.SECURE_FRP_MODE, 0) == 1;
+            }
 
             if (isSecureFrpEnabled
                     && !isSecureFrpInstallAllowed(mContext, Binder.getCallingUid())) {
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/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 266418f..ecd7035 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3526,6 +3526,9 @@
                         moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyEvent(event),
                                 true /* leftOrTop */);
                         logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION);
+                    } else if (event.isAltPressed()) {
+                        setSplitscreenFocus(true /* leftOrTop */);
+                        logKeyboardSystemsEvent(event, KeyboardLogEvent.CHANGE_SPLITSCREEN_FOCUS);
                     } else {
                         logKeyboardSystemsEvent(event, KeyboardLogEvent.BACK);
                         injectBackGesture(event.getDownTime());
@@ -3534,11 +3537,17 @@
                 }
                 break;
             case KeyEvent.KEYCODE_DPAD_RIGHT:
-                if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
-                    moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyEvent(event),
-                            false /* leftOrTop */);
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION);
-                    return true;
+                if (firstDown && event.isMetaPressed()) {
+                    if (event.isCtrlPressed()) {
+                        moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyEvent(event),
+                                false /* leftOrTop */);
+                        logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION);
+                        return true;
+                    } else if (event.isAltPressed()) {
+                        setSplitscreenFocus(false /* leftOrTop */);
+                        logKeyboardSystemsEvent(event, KeyboardLogEvent.CHANGE_SPLITSCREEN_FOCUS);
+                        return true;
+                    }
                 }
                 break;
             case KeyEvent.KEYCODE_SLASH:
@@ -4398,6 +4407,13 @@
         }
     }
 
+    private void setSplitscreenFocus(boolean leftOrTop) {
+        StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
+        if (statusbar != null) {
+            statusbar.setSplitscreenFocus(leftOrTop);
+        }
+    }
+
     void launchHomeFromHotKey(int displayId) {
         launchHomeFromHotKey(displayId, true /* awakenFromDreams */, true /*respectKeyguard*/);
     }
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index b50e2bf..6ff8cf3 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -56,6 +56,7 @@
 import android.database.ContentObserver;
 import android.hardware.SensorManager;
 import android.hardware.SystemSensorManager;
+import android.hardware.devicestate.DeviceState;
 import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.hardware.display.DisplayManagerInternal;
@@ -7152,9 +7153,10 @@
         private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
 
         @Override
-        public void onStateChanged(int deviceState) {
-            if (mDeviceState != deviceState) {
-                mDeviceState = deviceState;
+        public void onDeviceStateChanged(@NonNull DeviceState deviceState) {
+            int stateIdentifier = deviceState.getIdentifier();
+            if (mDeviceState != stateIdentifier) {
+                mDeviceState = stateIdentifier;
                 // Device-state interactions are applied to the default display so that they
                 // are reflected only with the default power group.
                 userActivityInternal(Display.DEFAULT_DISPLAY, mClock.uptimeMillis(),
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/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index c73f89c..f7c236a 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -238,6 +238,14 @@
     void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop);
 
     /**
+     * Change the split screen focus to the left / top app or the right / bottom app based on
+     * {@param leftOrTop}.
+     *
+     * @see com.android.internal.statusbar.IStatusBar#setSplitscreenFocus
+     */
+    void setSplitscreenFocus(boolean leftOrTop);
+
+    /**
      * Shows the media output switcher dialog.
      *
      * @param packageName of the session for which the output switcher is shown.
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 214dbe0..7b3e237 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -830,6 +830,15 @@
         }
 
         @Override
+        public void setSplitscreenFocus(boolean leftOrTop) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
+                try {
+                    bar.setSplitscreenFocus(leftOrTop);
+                } catch (RemoteException ex) { }
+            }
+        }
+        @Override
         public void enterDesktop(int displayId) {
             IStatusBar bar = mBar;
             if (bar != null) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index 0165d65..65ab129 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -314,6 +314,11 @@
             wallpaper.wallpaperId = makeWallpaperIdLocked();
         }
 
+        Rect legacyCropHint = new Rect(
+                getAttributeInt(parser, "cropLeft", 0),
+                getAttributeInt(parser, "cropTop", 0),
+                getAttributeInt(parser, "cropRight", 0),
+                getAttributeInt(parser, "cropBottom", 0));
         Rect totalCropHint = new Rect(
                 getAttributeInt(parser, "totalCropLeft", 0),
                 getAttributeInt(parser, "totalCropTop", 0),
@@ -332,18 +337,19 @@
                         parser.getAttributeInt(null, "cropBottom" + pair.second, 0));
                 if (!cropHint.isEmpty()) wallpaper.mCropHints.put(pair.first, cropHint);
             }
-            if (wallpaper.mCropHints.size() == 0) {
+            if (wallpaper.mCropHints.size() == 0 && totalCropHint.isEmpty()) {
                 // migration case: the crops per screen orientation are not specified.
-                // use the old attributes to find the crop for one screen orientation.
-                Integer orientation = totalCropHint.width() < totalCropHint.height()
+                int orientation = legacyCropHint.width() < legacyCropHint.height()
                         ? WallpaperManager.PORTRAIT : WallpaperManager.LANDSCAPE;
-                if (!totalCropHint.isEmpty()) wallpaper.mCropHints.put(orientation, totalCropHint);
+                if (!legacyCropHint.isEmpty()) {
+                    wallpaper.mCropHints.put(orientation, legacyCropHint);
+                }
             } else {
                 wallpaper.cropHint.set(totalCropHint);
             }
             wallpaper.mSampleSize = parser.getAttributeFloat(null, "sampleSize", 1f);
         } else {
-            wallpaper.cropHint.set(totalCropHint);
+            wallpaper.cropHint.set(legacyCropHint);
         }
         final DisplayData wpData = mWallpaperDisplayHelper
                 .getDisplayDataOrCreate(DEFAULT_DISPLAY);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 0069cdd..04e2988 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -328,6 +328,7 @@
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.service.contentcapture.ActivityEvent;
@@ -1003,6 +1004,10 @@
     // Whether the Activity allows state sharing in untrusted embedding
     private final boolean mAllowUntrustedEmbeddingStateSharing;
 
+    // TODO(b/329378309): Remove this once the overview handles the configuration correctly.
+    private static final boolean OVERRIDE_OVERVIEW_CONFIGURATION =
+            SystemProperties.getBoolean("persist.wm.debug.override_overview_configuration", true);
+
     // Records whether client has overridden the WindowAnimation_(Open/Close)(Enter/Exit)Animation.
     private CustomAppTransition mCustomOpenTransition;
     private CustomAppTransition mCustomCloseTransition;
@@ -7786,8 +7791,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) {
@@ -8605,7 +8613,12 @@
         if (rotation == ROTATION_UNDEFINED && !isFixedRotationTransforming()) {
             rotation = mDisplayContent.getRotation();
         }
-        if (!mWmService.mFlags.mInsetsDecoupledConfiguration
+        final int activityType = inOutConfig.windowConfiguration.getActivityType();
+        if (OVERRIDE_OVERVIEW_CONFIGURATION
+                && (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS)) {
+            // Do not early return and provide the override. This should be removed shortly as we
+            // don't override 1P components.
+        } else if (!mWmService.mFlags.mInsetsDecoupledConfiguration
                 || info.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED)
                 || getCompatDisplayInsets() != null
                 || isFloating(parentWindowingMode) || fullBounds == 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..4ec2f57 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -75,7 +75,7 @@
 import static com.android.server.wm.ActivityTaskManagerService.ANIMATE;
 import static com.android.server.wm.ActivityTaskManagerService.H.FIRST_SUPERVISOR_TASK_MSG;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
-import static com.android.server.wm.ClientLifecycleManager.shouldDispatchCompatClientTransactionIndependently;
+import static com.android.server.wm.ClientLifecycleManager.shouldDispatchLaunchActivityItemIndependently;
 import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_ALLOWLISTED;
 import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE;
 import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
@@ -953,7 +953,7 @@
                 }
 
                 // Schedule transaction.
-                if (shouldDispatchCompatClientTransactionIndependently(r.mTargetSdk)) {
+                if (shouldDispatchLaunchActivityItemIndependently(r.info.packageName, r.getUid())) {
                     // LaunchActivityItem has @UnsupportedAppUsage usages.
                     // Guard the bundleClientTransactionFlag feature with targetSDK on Android 15+.
                     // To not bundle the transaction, dispatch the pending before schedule new
@@ -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/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 1a63f14..5184e49 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -228,11 +228,8 @@
     private int mAppTransitionState = APP_STATE_IDLE;
 
     private final ArrayList<AppTransitionListener> mListeners = new ArrayList<>();
-    private KeyguardExitAnimationStartListener mKeyguardExitAnimationStartListener;
     private final ExecutorService mDefaultExecutor = Executors.newSingleThreadExecutor();
 
-    private final boolean mGridLayoutRecentsEnabled;
-
     private final int mDefaultWindowAnimationStyleResId;
     private boolean mOverrideTaskTransition;
 
@@ -249,8 +246,6 @@
         mTransitionAnimation = new TransitionAnimation(
                 context, ProtoLog.isEnabled(WM_DEBUG_ANIM), TAG);
 
-        mGridLayoutRecentsEnabled = SystemProperties.getBoolean("ro.recents.grid", false);
-
         final TypedArray windowStyle = mContext.getTheme().obtainStyledAttributes(
                 com.android.internal.R.styleable.Window);
         mDefaultWindowAnimationStyleResId = windowStyle.getResourceId(
@@ -493,11 +488,6 @@
         mListeners.remove(listener);
     }
 
-    void registerKeygaurdExitAnimationStartListener(
-            KeyguardExitAnimationStartListener listener) {
-        mKeyguardExitAnimationStartListener = listener;
-    }
-
     public void notifyAppTransitionFinishedLocked(IBinder token) {
         for (int i = 0; i < mListeners.size(); i++) {
             mListeners.get(i).onAppTransitionFinishedLocked(token);
@@ -1595,14 +1585,6 @@
         return mNextAppTransitionRequests.contains(transit);
     }
 
-    /**
-     * @return whether the transition should show the thumbnail being scaled down.
-     */
-    private boolean shouldScaleDownThumbnailTransition(int uiMode, int orientation) {
-        return mGridLayoutRecentsEnabled
-                || orientation == Configuration.ORIENTATION_PORTRAIT;
-    }
-
     private void handleAppTransitionTimeout() {
         synchronized (mService.mGlobalLock) {
             final DisplayContent dc = mDisplayContent;
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 6ed8967..e7fb265 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -642,7 +642,7 @@
             // by drawing the rotated content before applying projection transaction of display.
             // And it will fade in after the display transition is finished.
             if (mTransitionOp == OP_APP_SWITCH && !mIsStartTransactionCommitted
-                    && canBeAsync(w.mToken)) {
+                    && canBeAsync(w.mToken) && !mDisplayContent.hasFixedRotationTransientLaunch()) {
                 hideImmediately(w.mToken, Operation.ACTION_FADE);
                 if (DEBUG) Slog.d(TAG, "Hide on finishDrawing " + w.mToken.getTopChild());
             }
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/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
index 816fe1d..e396f2b 100644
--- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java
+++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
@@ -18,14 +18,19 @@
 
 import android.annotation.NonNull;
 import android.app.IApplicationThread;
+import android.app.compat.CompatChanges;
 import android.app.servertransaction.ActivityLifecycleItem;
 import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.ClientTransactionItem;
+import android.app.servertransaction.LaunchActivityItem;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
 import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.Trace;
+import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.Slog;
 
@@ -42,6 +47,15 @@
 
     private static final String TAG = "ClientLifecycleManager";
 
+    /**
+     * To prevent any existing apps from having app compat issue with the non-sdk usages of
+     * {@link ClientTransaction#getActivityToken()}, only allow bundling {@link LaunchActivityItem}
+     * for apps with targetSDK of V and above.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    private static final long ENABLE_BUNDLE_LAUNCH_ACTIVITY_ITEM = 324203798L;
+
     /** Mapping from client process binder to its pending transaction. */
     @VisibleForTesting
     final ArrayMap<IBinder, ClientTransaction> mPendingTransactions = new ArrayMap<>();
@@ -251,16 +265,11 @@
                 && !mWms.mWindowPlacerLocked.isInLayout();
     }
 
-    /**
-     * Guards the bundleClientTransactionFlag feature with targetSDK on Android 15+.
-     *
-     * Suppressing because it can't guard with @EnabledSince on VANILLA_ICE_CREAM yet since the
-     * version is not published.
-     *
-     * TODO(b/324203798): update in V
-     */
-    @SuppressWarnings("AndroidFrameworkCompatChange")
-    static boolean shouldDispatchCompatClientTransactionIndependently(int appTargetSdk) {
-        return appTargetSdk <= Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+    /** Guards bundling {@link LaunchActivityItem} with targetSDK. */
+    static boolean shouldDispatchLaunchActivityItemIndependently(
+            @NonNull String appPackageName, int appUid) {
+        return !CompatChanges.isChangeEnabled(ENABLE_BUNDLE_LAUNCH_ACTIVITY_ITEM,
+                appPackageName,
+                UserHandle.getUserHandleForUid(appUid));
     }
 }
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index b616d24..8020516 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -157,6 +157,10 @@
         }
     }
 
+    void onMirrorOutputSurfaceOrientationChanged() {
+        onConfigurationChanged(mLastOrientation, mLastWindowingMode);
+    }
+
     /**
      * Handle a configuration change on the display content, and resize recording if needed.
      * @param lastOrientation the prior orientation of the configuration
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 837d08b..56e6107 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1950,6 +1950,12 @@
                 && mFixedRotationLaunchingApp != mFixedRotationTransitionListener.mAnimatingRecents;
     }
 
+    /** It usually means whether the recents activity is launching with a different rotation. */
+    boolean hasFixedRotationTransientLaunch() {
+        return mFixedRotationLaunchingApp != null
+                && mTransitionController.isTransientLaunch(mFixedRotationLaunchingApp);
+    }
+
     boolean isFixedRotationLaunchingApp(ActivityRecord r) {
         return mFixedRotationLaunchingApp == r;
     }
@@ -6835,6 +6841,12 @@
         return mContentRecorder;
     }
 
+    void onMirrorOutputSurfaceOrientationChanged() {
+        if (mContentRecorder != null) {
+            mContentRecorder.onMirrorOutputSurfaceOrientationChanged();
+        }
+    }
+
     /**
      * Pause the recording session.
      */
@@ -7013,9 +7025,8 @@
         boolean shouldDeferRotation() {
             ActivityRecord source = null;
             if (mTransitionController.isShellTransitionsEnabled()) {
-                final ActivityRecord r = mFixedRotationLaunchingApp;
-                if (r != null && mTransitionController.isTransientLaunch(r)) {
-                    source = r;
+                if (hasFixedRotationTransientLaunch()) {
+                    source = mFixedRotationLaunchingApp;
                 }
             } else if (mAnimatingRecents != null && !hasTopFixedRotationLaunchingApp()) {
                 source = mAnimatingRecents;
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 384b91a..4a97128 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -19,6 +19,8 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.view.Display.TYPE_EXTERNAL;
+import static android.view.Display.TYPE_OVERLAY;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
@@ -915,6 +917,11 @@
                     + " for " + mDisplayContent);
             userRotation = Surface.ROTATION_0;
         }
+        final int userRotationOverride = getUserRotationOverride();
+        if (userRotationOverride != 0) {
+            userRotationMode = WindowManagerPolicy.USER_ROTATION_LOCKED;
+            userRotation = userRotationOverride;
+        }
         mUserRotationMode = userRotationMode;
         mUserRotation = userRotation;
     }
@@ -965,6 +972,13 @@
         if (changed) {
             mService.updateRotation(false /* alwaysSendConfiguration */,
                     false /* forceRelayout */);
+            // ContentRecorder.onConfigurationChanged and Device.setProjectionLocked are called
+            // during updateRotation above. But onConfigurationChanged is called before
+            // Device.setProjectionLocked, which means that the onConfigurationChanged will
+            // not have the new rotation when it calls getDisplaySurfaceDefaultSize.
+            // To make sure that mirroring takes the new rotation of the output surface
+            // into account we need to call onConfigurationChanged again.
+            mDisplayContent.onMirrorOutputSurfaceOrientationChanged();
         }
     }
 
@@ -1780,6 +1794,23 @@
         }
     }
 
+    @Surface.Rotation
+    private int getUserRotationOverride() {
+        final int userRotationOverride = SystemProperties.getInt("persist.demo.userrotation",
+                Surface.ROTATION_0);
+        if (userRotationOverride == Surface.ROTATION_0) {
+            return userRotationOverride;
+        }
+
+        final var display = mDisplayContent.mDisplay;
+        if (display.getType() == TYPE_EXTERNAL || display.getType() == TYPE_OVERLAY) {
+            // TODO b/329442350 add chromecast virtual displays here
+            return userRotationOverride;
+        }
+
+        return Surface.ROTATION_0;
+    }
+
     @VisibleForTesting
     long uptimeMillis() {
         return SystemClock.uptimeMillis();
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 5d613cf..a24c02a 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -22,6 +22,7 @@
 import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION;
 import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION_TO_USER;
 import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT;
 import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
 import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
 import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS;
@@ -131,6 +132,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.LetterboxDetails;
 import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType;
+import com.android.window.flags.Flags;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -194,7 +196,8 @@
     private final boolean mIsOverrideCameraCompatDisableRefreshEnabled;
     // Corresponds to OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE
     private final boolean mIsOverrideCameraCompatEnableRefreshViaPauseEnabled;
-
+    // Corresponds to OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT
+    private final boolean mIsOverrideCameraCompatDisableFreeformWindowingTreatmentEnabled;
     // Corresponds to OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION
     private final boolean mIsOverrideEnableCompatIgnoreRequestedOrientationEnabled;
     // Corresponds to OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED
@@ -321,15 +324,15 @@
                         PROPERTY_COMPAT_ENABLE_FAKE_FOCUS);
         mBooleanPropertyCameraCompatAllowForceRotation =
                 readComponentProperty(packageManager, mActivityRecord.packageName,
-                        () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled(),
+                        mLetterboxConfiguration::isCameraCompatTreatmentEnabled,
                         PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION);
         mBooleanPropertyCameraCompatAllowRefresh =
                 readComponentProperty(packageManager, mActivityRecord.packageName,
-                        () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled(),
+                        mLetterboxConfiguration::isCameraCompatTreatmentEnabled,
                         PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH);
         mBooleanPropertyCameraCompatEnableRefreshViaPause =
                 readComponentProperty(packageManager, mActivityRecord.packageName,
-                        () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled(),
+                        mLetterboxConfiguration::isCameraCompatTreatmentEnabled,
                         PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE);
 
         mBooleanPropertyAllowOrientationOverride =
@@ -351,11 +354,11 @@
 
         mBooleanPropertyAllowUserAspectRatioOverride =
                 readComponentProperty(packageManager, mActivityRecord.packageName,
-                        () -> mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled(),
+                        mLetterboxConfiguration::isUserAppAspectRatioSettingsEnabled,
                         PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE);
         mBooleanPropertyAllowUserAspectRatioFullscreenOverride =
                 readComponentProperty(packageManager, mActivityRecord.packageName,
-                        () -> mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled(),
+                        mLetterboxConfiguration::isUserAppAspectRatioFullscreenEnabled,
                         PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE);
 
         mIsOverrideAnyOrientationEnabled = isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION);
@@ -380,6 +383,8 @@
                 isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH);
         mIsOverrideCameraCompatEnableRefreshViaPauseEnabled =
                 isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE);
+        mIsOverrideCameraCompatDisableFreeformWindowingTreatmentEnabled =
+                isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT);
 
         mIsOverrideEnableCompatIgnoreRequestedOrientationEnabled =
                 isCompatChangeEnabled(OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION);
@@ -759,8 +764,7 @@
      */
     boolean shouldRefreshActivityForCameraCompat() {
         return shouldEnableWithOptOutOverrideAndProperty(
-                /* gatingCondition */ () -> mLetterboxConfiguration
-                        .isCameraCompatTreatmentEnabled(),
+                /* gatingCondition */ mLetterboxConfiguration::isCameraCompatTreatmentEnabled,
                 mIsOverrideCameraCompatDisableRefreshEnabled,
                 mBooleanPropertyCameraCompatAllowRefresh);
     }
@@ -781,8 +785,7 @@
      */
     boolean shouldRefreshActivityViaPauseForCameraCompat() {
         return shouldEnableWithOverrideAndProperty(
-                /* gatingCondition */ () -> mLetterboxConfiguration
-                        .isCameraCompatTreatmentEnabled(),
+                /* gatingCondition */ mLetterboxConfiguration::isCameraCompatTreatmentEnabled,
                 mIsOverrideCameraCompatEnableRefreshViaPauseEnabled,
                 mBooleanPropertyCameraCompatEnableRefreshViaPause);
     }
@@ -800,12 +803,34 @@
      */
     boolean shouldForceRotateForCameraCompat() {
         return shouldEnableWithOptOutOverrideAndProperty(
-                /* gatingCondition */ () -> mLetterboxConfiguration
-                        .isCameraCompatTreatmentEnabled(),
+                /* gatingCondition */ mLetterboxConfiguration::isCameraCompatTreatmentEnabled,
                 mIsOverrideCameraCompatDisableForceRotationEnabled,
                 mBooleanPropertyCameraCompatAllowForceRotation);
     }
 
+    /**
+     * Whether activity is eligible for camera compatibility free-form treatment.
+     *
+     * <p>The treatment is applied to a fixed-orientation camera activity in free-form windowing
+     * mode. The treatment letterboxes or pillarboxes the activity to the expected orientation and
+     * provides changes to the camera and display orientation signals to match those expected on a
+     * portrait device in that orientation (for example, on a standard phone).
+     *
+     * <p>The treatment is enabled when the following conditions are met:
+     * <ul>
+     * <li>Property gating the camera compatibility free-form treatment is enabled.
+     * <li>Activity isn't opted out by the device manufacturer with override or by the app
+     * developers with the component property.
+     * </ul>
+     */
+    boolean shouldApplyFreeformTreatmentForCameraCompat() {
+        return shouldEnableWithOptOutOverrideAndProperty(
+                /* gatingCondition */ ()-> Flags.cameraCompatForFreeform(),
+                mIsOverrideCameraCompatDisableFreeformWindowingTreatmentEnabled,
+                // TODO(b/328616176): add a manifest override for developers.
+                null);
+    }
+
     private boolean isCameraCompatTreatmentActive() {
         DisplayContent displayContent = mActivityRecord.mDisplayContent;
         if (displayContent == null) {
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index dd14642..33588a0 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -364,11 +364,6 @@
                     com.android.internal.R.integer.config_minNumVisibleRecentTasks_lowRam);
             mMaxNumVisibleTasks = res.getInteger(
                     com.android.internal.R.integer.config_maxNumVisibleRecentTasks_lowRam);
-        } else if (SystemProperties.getBoolean("ro.recents.grid", false)) {
-            mMinNumVisibleTasks = res.getInteger(
-                    com.android.internal.R.integer.config_minNumVisibleRecentTasks_grid);
-            mMaxNumVisibleTasks = res.getInteger(
-                    com.android.internal.R.integer.config_maxNumVisibleRecentTasks_grid);
         } else {
             mMinNumVisibleTasks = res.getInteger(
                     com.android.internal.R.integer.config_minNumVisibleRecentTasks);
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/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 3fb5998..a2f6fb4 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -336,7 +336,7 @@
         for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) {
             final WallpaperWindowToken token = mWallpaperTokens.get(i);
             token.setVisibility(false);
-            if (ProtoLog.isEnabled(WM_DEBUG_WALLPAPER) && token.isVisible()) {
+            if (token.isVisible()) {
                 ProtoLog.d(WM_DEBUG_WALLPAPER,
                         "Hiding wallpaper %s from %s target=%s prev=%s callers=%s",
                         token, winGoingAway, mWallpaperTarget, mPrevWallpaperTarget,
@@ -527,15 +527,15 @@
                         if ((mLastWallpaperTimeoutTime + WALLPAPER_TIMEOUT_RECOVERY)
                                 < start) {
                             try {
-                                if (DEBUG_WALLPAPER) Slog.v(TAG,
-                                        "Waiting for offset complete...");
+                                ProtoLog.v(WM_DEBUG_WALLPAPER, "Waiting for offset complete...");
                                 mService.mGlobalLock.wait(WALLPAPER_TIMEOUT);
                             } catch (InterruptedException e) {
                             }
-                            if (DEBUG_WALLPAPER) Slog.v(TAG, "Offset complete!");
+                            ProtoLog.v(WM_DEBUG_WALLPAPER, "Offset complete!");
                             if ((start + WALLPAPER_TIMEOUT) < SystemClock.uptimeMillis()) {
-                                Slog.i(TAG, "Timeout waiting for wallpaper to offset: "
-                                        + wallpaperWin);
+                                ProtoLog.v(WM_DEBUG_WALLPAPER,
+                                        "Timeout waiting for wallpaper to offset: %s",
+                                        wallpaperWin);
                                 mLastWallpaperTimeoutTime = start;
                             }
                         }
@@ -891,10 +891,6 @@
         // The window is visible to the compositor...but is it visible to the user?
         // That is what the wallpaper cares about.
         final boolean visible = token != null;
-        if (DEBUG_WALLPAPER) {
-            Slog.v(TAG, "Wallpaper visibility: " + visible + " at display "
-                    + mDisplayContent.getDisplayId());
-        }
 
         if (visible) {
             if (mWallpaperTarget.mWallpaperX >= 0) {
@@ -915,10 +911,9 @@
 
         updateWallpaperTokens(visible, mDisplayContent.isKeyguardLocked());
 
-        if (DEBUG_WALLPAPER) {
-            Slog.v(TAG, "adjustWallpaperWindows: wallpaper visibility " + visible
-                    + ", lock visibility " + mDisplayContent.isKeyguardLocked());
-        }
+        ProtoLog.v(WM_DEBUG_WALLPAPER,
+                "Wallpaper at display %d - visibility: %b, keyguardLocked: %b",
+                mDisplayContent.getDisplayId(), visible, mDisplayContent.isKeyguardLocked());
 
         if (visible && mLastFrozen != mFindResults.isWallpaperTargetForLetterbox) {
             mLastFrozen = mFindResults.isWallpaperTargetForLetterbox;
@@ -927,7 +922,7 @@
                     /* x= */ 0, /* y= */ 0, /* z= */ 0, /* extras= */ null, /* sync= */ false);
         }
 
-        ProtoLog.d(WM_DEBUG_WALLPAPER, "New wallpaper: target=%s prev=%s",
+        ProtoLog.d(WM_DEBUG_WALLPAPER, "Wallpaper target=%s prev=%s",
                 mWallpaperTarget, mPrevWallpaperTarget);
     }
 
@@ -973,11 +968,9 @@
                                 WALLPAPER_DRAW_PENDING_TIMEOUT_DURATION);
 
                 }
-                if (DEBUG_WALLPAPER) {
-                    Slog.v(TAG,
-                            "Wallpaper should be visible but has not been drawn yet. "
-                                    + "mWallpaperDrawState=" + mWallpaperDrawState);
-                }
+                ProtoLog.v(WM_DEBUG_WALLPAPER,
+                        "Wallpaper should be visible but has not been drawn yet. "
+                                + "mWallpaperDrawState=%d", mWallpaperDrawState);
                 break;
             }
         }
@@ -1210,15 +1203,17 @@
         boolean isWallpaperTargetForLetterbox = false;
 
         void setTopHideWhenLockedWallpaper(WindowState win) {
-            if (DEBUG_WALLPAPER) {
-                Slog.v(TAG, "setTopHideWhenLockedWallpaper " + win);
+            if (mTopWallpaper.mTopHideWhenLockedWallpaper != win) {
+                ProtoLog.d(WM_DEBUG_WALLPAPER, "New home screen wallpaper: %s, prev: %s",
+                        win, mTopWallpaper.mTopHideWhenLockedWallpaper);
             }
             mTopWallpaper.mTopHideWhenLockedWallpaper = win;
         }
 
         void setTopShowWhenLockedWallpaper(WindowState win) {
-            if (DEBUG_WALLPAPER) {
-                Slog.v(TAG, "setTopShowWhenLockedWallpaper " + win);
+            if (mTopWallpaper.mTopShowWhenLockedWallpaper != win) {
+                ProtoLog.d(WM_DEBUG_WALLPAPER, "New lock/shared screen wallpaper: %s, prev: %s",
+                        win, mTopWallpaper.mTopShowWhenLockedWallpaper);
             }
             mTopWallpaper.mTopShowWhenLockedWallpaper = win;
         }
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 77319cc..acc6330 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -620,14 +620,6 @@
     public abstract void unregisterTaskSystemBarsListener(TaskSystemBarsListener listener);
 
     /**
-     * Registers a listener to be notified to start the keyguard exit animation.
-     *
-     * @param listener The listener to register.
-     */
-    public abstract void registerKeyguardExitAnimationStartListener(
-            KeyguardExitAnimationStartListener listener);
-
-    /**
      * Reports that the password for the given user has changed.
      */
     public abstract void reportPasswordChanged(int userId);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 71ffabf..0e66fcf 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8094,15 +8094,6 @@
         }
 
         @Override
-        public void registerKeyguardExitAnimationStartListener(
-                KeyguardExitAnimationStartListener listener) {
-            synchronized (mGlobalLock) {
-                getDefaultDisplayContentLocked().mAppTransition
-                        .registerKeygaurdExitAnimationStartListener(listener);
-            }
-        }
-
-        @Override
         public void reportPasswordChanged(int userId) {
             mKeyguardDisableHandler.updateKeyguardEnabled(userId);
         }
@@ -9098,7 +9089,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 +9118,13 @@
                 // If the transferToToken exists in the input to window map, it means the request
                 // is to transfer from embedded to host. Otherwise, the transferToToken
                 // represents an embedded window so transfer from host to embedded.
-                WindowState windowStateTo = mInputToWindowMap.get(transferToToken.mToken);
+                WindowState windowStateTo = mInputToWindowMap.get(transferToToken.getToken());
                 if (windowStateTo != null) {
                     didTransfer = mEmbeddedWindowController.transferToHost(transferFromToken,
                             windowStateTo);
                 } else {
-                    WindowState windowStateFrom = mInputToWindowMap.get(transferFromToken.mToken);
+                    WindowState windowStateFrom = mInputToWindowMap.get(
+                            transferFromToken.getToken());
                     didTransfer = mEmbeddedWindowController.transferToEmbedded(windowStateFrom,
                             transferToToken);
                 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index a6db310f..731184f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -519,6 +519,9 @@
             case "default":
                 fixedToUserRotation = IWindowManager.FIXED_TO_USER_ROTATION_DEFAULT;
                 break;
+            case "enabled_if_no_auto_rotation":
+                fixedToUserRotation = IWindowManager.FIXED_TO_USER_ROTATION_IF_NO_AUTO_ROTATION;
+                break;
             default:
                 getErrPrintWriter().println("Error: expecting enabled, disabled or default, but we "
                         + "get " + arg);
@@ -538,6 +541,9 @@
             case IWindowManager.FIXED_TO_USER_ROTATION_DISABLED:
                 pw.println("disabled");
                 return 0;
+            case IWindowManager.FIXED_TO_USER_ROTATION_IF_NO_AUTO_ROTATION:
+                pw.println("enabled_if_no_auto_rotation");
+                return 0;
             case IWindowManager.FIXED_TO_USER_ROTATION_ENABLED:
                 pw.println("enabled");
                 return 0;
@@ -1494,7 +1500,8 @@
         pw.println("    Print or set user rotation mode and user rotation.");
         pw.println("  dump-visible-window-views");
         pw.println("    Dumps the encoded view hierarchies of visible windows");
-        pw.println("  fixed-to-user-rotation [-d DISPLAY_ID] [enabled|disabled|default]");
+        pw.println("  fixed-to-user-rotation [-d DISPLAY_ID] [enabled|disabled|default");
+        pw.println("      |enabled_if_no_auto_rotation]");
         pw.println("    Print or set rotating display for app requested orientation.");
         pw.println("  set-ignore-orientation-request [-d DISPLAY_ID] [true|1|false|0]");
         pw.println("  get-ignore-orientation-request [-d DISPLAY_ID] ");
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index a63e106..d967cde 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -34,6 +34,7 @@
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_COMPANION_TASK_FRAGMENT;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_DECOR_SURFACE_BOOSTED;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATION;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH;
@@ -124,6 +125,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.server.LocalServices;
 import com.android.server.pm.LauncherAppsService.LauncherAppsServiceInternal;
+import com.android.window.flags.Flags;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -1557,13 +1559,11 @@
                 break;
             }
             case OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE: {
-                final Task task = taskFragment.getTask();
-                task.moveOrCreateDecorSurfaceFor(taskFragment);
+                taskFragment.getTask().moveOrCreateDecorSurfaceFor(taskFragment);
                 break;
             }
             case OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE: {
-                final Task task = taskFragment.getTask();
-                task.removeDecorSurface();
+                taskFragment.getTask().removeDecorSurface();
                 break;
             }
             case OP_TYPE_SET_DIM_ON_TASK: {
@@ -1577,6 +1577,23 @@
                         operation.isMoveToBottomIfClearWhenLaunch());
                 break;
             }
+            case OP_TYPE_SET_DECOR_SURFACE_BOOSTED: {
+                if (Flags.activityEmbeddingInteractiveDividerFlag()) {
+                    final SurfaceControl.Transaction clientTransaction =
+                            operation.getSurfaceTransaction();
+                    if (clientTransaction != null) {
+                        // Sanitize the client transaction. sanitize() silently removes invalid
+                        // operations and does not throw or provide signal about whether there are
+                        // any invalid operations.
+                        clientTransaction.sanitize(caller.mPid, caller.mUid);
+                    }
+                    taskFragment.getTask().setDecorSurfaceBoosted(
+                            taskFragment,
+                            operation.getBooleanValue() /* isBoosted */,
+                            clientTransaction);
+                }
+                break;
+            }
         }
         return effects;
     }
@@ -1616,19 +1633,6 @@
             return false;
         }
 
-        // TODO (b/293654166) remove the decor surface checks once we clear security reviews
-        if ((opType == OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE
-                || opType == OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE)
-                && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
-            final Throwable exception = new SecurityException(
-                    "Only a system organizer can perform OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE"
-                            + " or OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE."
-            );
-            sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
-                    opType, exception);
-            return false;
-        }
-
         if ((opType == OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH)
                 && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
             final Throwable exception = new SecurityException(
@@ -1766,6 +1770,13 @@
                 throw new RuntimeException("Reparenting leaf Tasks is not supported now. " + task);
             }
         } else {
+            if (hop.getToTop() && task.isRootTask()) {
+                final ActivityRecord pipCandidate = task.findEnterPipOnTaskSwitchCandidate(
+                        task.getDisplayArea().getTopRootTask());
+                task.enableEnterPipOnTaskSwitch(pipCandidate, task, null /* toFrontActivity */,
+                        null /* options */);
+            }
+
             task.getParent().positionChildAt(
                     hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM,
                     task, false /* includingParents */);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 46bac16..2b337ae 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5700,7 +5700,8 @@
             // window becomes visible while the sync group is still active.
             return true;
         }
-        if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mLastConfigReportedToClient && isDrawn()) {
+        if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mLastConfigReportedToClient && isDrawn()
+                && mPrepareSyncSeqId <= 0) {
             // Complete the sync state immediately for a drawn window that doesn't need to redraw.
             onSyncFinishedDrawing();
         }
diff --git a/services/core/jni/com_android_server_UsbDeviceManager.cpp b/services/core/jni/com_android_server_UsbDeviceManager.cpp
index 0a9ce2f..9dc70af 100644
--- a/services/core/jni/com_android_server_UsbDeviceManager.cpp
+++ b/services/core/jni/com_android_server_UsbDeviceManager.cpp
@@ -108,18 +108,6 @@
     return (result == 1);
 }
 
-static jint android_server_UsbDeviceManager_getAudioMode(JNIEnv* /* env */, jobject /* thiz */)
-{
-    int fd = open(DRIVER_NAME, O_RDWR);
-    if (fd < 0) {
-        ALOGE("could not open %s", DRIVER_NAME);
-        return false;
-    }
-    int result = ioctl(fd, ACCESSORY_GET_AUDIO_MODE);
-    close(fd);
-    return result;
-}
-
 static jobject android_server_UsbDeviceManager_openControl(JNIEnv *env, jobject /* thiz */, jstring jFunction) {
     ScopedUtfChars function(env, jFunction);
     bool ptp = false;
@@ -148,16 +136,13 @@
 }
 
 static const JNINativeMethod method_table[] = {
-    { "nativeGetAccessoryStrings",  "()[Ljava/lang/String;",
-                                    (void*)android_server_UsbDeviceManager_getAccessoryStrings },
-    { "nativeOpenAccessory",        "()Landroid/os/ParcelFileDescriptor;",
-                                    (void*)android_server_UsbDeviceManager_openAccessory },
-    { "nativeIsStartRequested",     "()Z",
-                                    (void*)android_server_UsbDeviceManager_isStartRequested },
-    { "nativeGetAudioMode",         "()I",
-                                    (void*)android_server_UsbDeviceManager_getAudioMode },
-    { "nativeOpenControl",          "(Ljava/lang/String;)Ljava/io/FileDescriptor;",
-                                    (void*)android_server_UsbDeviceManager_openControl },
+        {"nativeGetAccessoryStrings", "()[Ljava/lang/String;",
+         (void *)android_server_UsbDeviceManager_getAccessoryStrings},
+        {"nativeOpenAccessory", "()Landroid/os/ParcelFileDescriptor;",
+         (void *)android_server_UsbDeviceManager_openAccessory},
+        {"nativeIsStartRequested", "()Z", (void *)android_server_UsbDeviceManager_isStartRequested},
+        {"nativeOpenControl", "(Ljava/lang/String;)Ljava/io/FileDescriptor;",
+         (void *)android_server_UsbDeviceManager_openControl},
 };
 
 int register_android_server_UsbDeviceManager(JNIEnv *env)
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/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index b38a2f9..d0df2b2 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -168,6 +168,10 @@
                 <xs:element type="nonNegativeDecimal" name="screenBrightnessCapForWearBedtimeMode">
                     <xs:annotation name="final"/>
                 </xs:element>
+                <!-- Timeout after which we reduce the refresh rate if the screen has been idle, in order to save power. -->
+                <xs:element type="idleScreenRefreshRateTimeout" name="idleScreenRefreshRateTimeout" minOccurs="0">
+                    <xs:annotation name="final"/>
+                </xs:element>
             </xs:sequence>
         </xs:complexType>
     </xs:element>
@@ -772,6 +776,30 @@
         </xs:sequence>
     </xs:complexType>
 
+    <xs:complexType name="idleScreenRefreshRateTimeout">
+        <xs:element name="luxThresholds" type="idleScreenRefreshRateTimeoutLuxThresholds" minOccurs="0">
+            <xs:annotation name="final"/>
+        </xs:element>
+    </xs:complexType>
+
+    <!-- Lux based timeout after which we reduce the refresh rate if the screen has been idle, in order to save power. -->
+    <xs:complexType name="idleScreenRefreshRateTimeoutLuxThresholds">
+        <xs:sequence>
+            <xs:element name="point" type="idleScreenRefreshRateTimeoutLuxThresholdPoint" maxOccurs="unbounded" />
+        </xs:sequence>
+    </xs:complexType>
+
+    <!-- Represents a tuple of lux and timeout(in ms), which represents the timeout value for the lux in
+    the [luxValue, nextLuxValue (INF if missing))  -->
+    <xs:complexType name="idleScreenRefreshRateTimeoutLuxThresholdPoint">
+        <xs:element name="lux" type="xs:nonNegativeInteger">
+            <xs:annotation name="final"/>
+        </xs:element>
+        <xs:element name="timeout" type="xs:nonNegativeInteger">
+            <xs:annotation name="final"/>
+        </xs:element>
+    </xs:complexType>
+
     <!-- Predefined type names as defined by
     AutomaticBrightnessController.AutomaticBrightnessMode -->
     <xs:simpleType  name="AutoBrightnessModeName">
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index b329db4..00dc908 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -111,6 +111,7 @@
     method public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholdsIdle();
     method @Nullable public final com.android.server.display.config.HdrBrightnessConfig getHdrBrightnessConfig();
     method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode();
+    method public final com.android.server.display.config.IdleScreenRefreshRateTimeout getIdleScreenRefreshRateTimeout();
     method public final com.android.server.display.config.SensorDetails getLightSensor();
     method public com.android.server.display.config.LuxThrottling getLuxThrottling();
     method @Nullable public final String getName();
@@ -146,6 +147,7 @@
     method public final void setDisplayBrightnessChangeThresholdsIdle(com.android.server.display.config.Thresholds);
     method public final void setHdrBrightnessConfig(@Nullable com.android.server.display.config.HdrBrightnessConfig);
     method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode);
+    method public final void setIdleScreenRefreshRateTimeout(com.android.server.display.config.IdleScreenRefreshRateTimeout);
     method public final void setLightSensor(com.android.server.display.config.SensorDetails);
     method public void setLuxThrottling(com.android.server.display.config.LuxThrottling);
     method public final void setName(@Nullable String);
@@ -222,6 +224,25 @@
     method public final void setTransitionPoint_all(@NonNull java.math.BigDecimal);
   }
 
+  public class IdleScreenRefreshRateTimeout {
+    ctor public IdleScreenRefreshRateTimeout();
+    method public final com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholds getLuxThresholds();
+    method public final void setLuxThresholds(com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholds);
+  }
+
+  public class IdleScreenRefreshRateTimeoutLuxThresholdPoint {
+    ctor public IdleScreenRefreshRateTimeoutLuxThresholdPoint();
+    method public final java.math.BigInteger getLux();
+    method public final java.math.BigInteger getTimeout();
+    method public final void setLux(java.math.BigInteger);
+    method public final void setTimeout(java.math.BigInteger);
+  }
+
+  public class IdleScreenRefreshRateTimeoutLuxThresholds {
+    ctor public IdleScreenRefreshRateTimeoutLuxThresholds();
+    method public java.util.List<com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint> getPoint();
+  }
+
   public class IntegerArray {
     ctor public IntegerArray();
     method public java.util.List<java.math.BigInteger> getItem();
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index 96ef2ed..bdea4f9 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -568,7 +568,10 @@
                     /* clicked_entries */ browsedClickedEntries,
                     /* provider_of_clicked_entry */ browsedProviderUid,
                     /* api_status */ apiStatus,
-                    /* primary_indicated */ finalPhaseMetric.isPrimary()
+                    /* primary_indicated */ finalPhaseMetric.isPrimary(),
+                    /* oem_credential_manager_ui_uid */ finalPhaseMetric.getOemUiUid(),
+                    /* fallback_credential_manager_ui_uid */ finalPhaseMetric.getFallbackUiUid(),
+                    /* oem_ui_usage_status */ finalPhaseMetric.getOemUiUsageStatus()
             );
         } catch (Exception e) {
             Slog.w(TAG, "Unexpected error during final no uid metric logging: " + e);
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index cad9a09..c5f2921 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -182,7 +182,7 @@
             CredentialProviderInfo info,
             String hybridService) {
         Slog.i(TAG, "Filtering request options for: " + info.getComponentName());
-        if (android.credentials.flags.Flags.hybridFilterFixEnabled()) {
+        if (android.credentials.flags.Flags.hybridFilterOptFixEnabled()) {
             ComponentName hybridComponentName = ComponentName.unflattenFromString(hybridService);
             if (hybridComponentName != null && hybridComponentName
                     .equals(info.getComponentName())) {
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
index 468d3c8..9dd6db6 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
@@ -67,6 +67,10 @@
 
     // Other General Information, such as final api status, provider status, entry info, etc...
 
+    private int mOemUiUid = -1;
+    private int mFallbackUiUid = -1;
+    private OemUiUsageStatus mOemUiUsageStatus = OemUiUsageStatus.UNKNOWN;
+
     private int mChosenProviderStatus = -1;
     // Indicates if an exception was thrown by this provider, false by default
     private boolean mHasException = false;
@@ -302,4 +306,28 @@
     public boolean isPrimary() {
         return mIsPrimary;
     }
+
+    public void setOemUiUid(int oemUiUid) {
+        mOemUiUid = oemUiUid;
+    }
+
+    public int getOemUiUid() {
+        return mOemUiUid;
+    }
+
+    public void setFallbackUiUid(int fallbackUiUid) {
+        mFallbackUiUid = fallbackUiUid;
+    }
+
+    public int getFallbackUiUid() {
+        return mFallbackUiUid;
+    }
+
+    public void setOemUiUsageStatus(OemUiUsageStatus oemUiUsageStatus) {
+        mOemUiUsageStatus = oemUiUsageStatus;
+    }
+
+    public int getOemUiUsageStatus() {
+        return mOemUiUsageStatus.getLoggingInt();
+    }
 }
diff --git a/services/credentials/java/com/android/server/credentials/metrics/OemUiUsageStatus.java b/services/credentials/java/com/android/server/credentials/metrics/OemUiUsageStatus.java
new file mode 100644
index 0000000..2fd3a86
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/metrics/OemUiUsageStatus.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.credentials.metrics;
+
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SUCCESS;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_NOT_SPECIFIED;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SPECIFIED_BUT_NOT_FOUND;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SPECIFIED_BUT_NOT_ENABLED;
+
+
+public enum OemUiUsageStatus {
+    UNKNOWN(CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_UNKNOWN),
+    SUCCESS(CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SUCCESS),
+    FAILURE_NOT_SPECIFIED(CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_NOT_SPECIFIED),
+    FAILURE_SPECIFIED_BUT_NOT_FOUND(CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SPECIFIED_BUT_NOT_FOUND),
+    FAILURE_SPECIFIED_BUT_NOT_ENABLED(CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SPECIFIED_BUT_NOT_ENABLED);
+
+    private final int mLoggingInt;
+
+    OemUiUsageStatus(int loggingInt) {
+        mLoggingInt = loggingInt;
+    }
+
+    public int getLoggingInt() {
+        return mLoggingInt;
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ee72db0..cb87f7e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -79,7 +79,6 @@
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SYSTEM_UPDATES;
-import static android.Manifest.permission.MANAGE_DEVICE_POLICY_THEFT_DETECTION;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_THREAD_NETWORK;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_TIME;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING;
@@ -93,6 +92,7 @@
 import static android.Manifest.permission.MASTER_CLEAR;
 import static android.Manifest.permission.NOTIFY_PENDING_SYSTEM_UPDATE;
 import static android.Manifest.permission.QUERY_ADMIN_POLICY;
+import static android.Manifest.permission.QUERY_DEVICE_STOLEN_STATE;
 import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY;
 import static android.Manifest.permission.SET_TIME;
 import static android.Manifest.permission.SET_TIME_ZONE;
@@ -3397,7 +3397,6 @@
                     }
                     maybeMigratePoliciesPostUpgradeToDevicePolicyEngineLocked();
                     migratePoliciesToPolicyEngineLocked();
-
                 }
                 maybeStartSecurityLogMonitorOnActivityManagerReady();
                 break;
@@ -12303,9 +12302,6 @@
         }
         final CallerIdentity caller = getCallerIdentity(admin);
 
-        // Only allow the system user to use this method
-        Preconditions.checkCallAuthorization(caller.getUserHandle().isSystem(),
-                "createAndManageUser was called from non-system user");
         Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_CREATE_AND_MANAGE_USER);
 
@@ -12316,6 +12312,10 @@
                     "createAndManageUser was called while in headless single user mode");
         }
 
+        // Only allow the system user to use this method
+        Preconditions.checkCallAuthorization(caller.getUserHandle().isSystem(),
+                "createAndManageUser was called from non-system user");
+
         final boolean ephemeral = (flags & DevicePolicyManager.MAKE_USER_EPHEMERAL) != 0;
         final boolean demo = (flags & DevicePolicyManager.MAKE_USER_DEMO) != 0
                 && UserManager.isDeviceInDemoMode(mContext);
@@ -13179,27 +13179,47 @@
             CallerIdentity caller, EnforcingAdmin admin, String key, boolean enabled,
             boolean parent) {
         synchronized (getLockObject()) {
+
+            int ownerType;
             if (isDeviceOwner(caller)) {
-                if (UserRestrictionsUtils.isGlobal(OWNER_TYPE_DEVICE_OWNER, key)) {
-                    setGlobalUserRestrictionInternal(admin, key, enabled);
-                } else {
-                    setLocalUserRestrictionInternal(admin, key, enabled, caller.getUserId());
-                }
+                ownerType = OWNER_TYPE_DEVICE_OWNER;
+            } else if (isProfileOwnerOfOrganizationOwnedDevice(caller)) {
+                ownerType = OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE;
             } else if (isProfileOwner(caller)) {
-                if (UserRestrictionsUtils.isGlobal(OWNER_TYPE_PROFILE_OWNER, key)
-                        || (parent && isProfileOwnerOfOrganizationOwnedDevice(caller)
-                        && UserRestrictionsUtils.isGlobal(
-                                OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE, key))) {
-                    setGlobalUserRestrictionInternal(admin, key, enabled);
-                } else {
-                    int affectedUserId = parent
-                            ? getProfileParentId(caller.getUserId()) : caller.getUserId();
-                    setLocalUserRestrictionInternal(admin, key, enabled, affectedUserId);
-                }
+                ownerType = OWNER_TYPE_PROFILE_OWNER;
             } else {
                 throw new IllegalStateException("Non-DO/Non-PO cannot set restriction " + key
                         + " while targetSdkVersion is less than UPSIDE_DOWN_CAKE");
             }
+            setBackwardCompatibleUserRestrictionLocked(ownerType, admin, caller.getUserId(), key,
+                    enabled, parent);
+        }
+    }
+
+    private void setBackwardCompatibleUserRestrictionLocked(
+            int ownerType, EnforcingAdmin admin, int userId, String key, boolean enabled,
+            boolean parent) {
+        if (ownerType == OWNER_TYPE_DEVICE_OWNER) {
+            if (UserRestrictionsUtils.isGlobal(OWNER_TYPE_DEVICE_OWNER, key)) {
+                setGlobalUserRestrictionInternal(admin, key, enabled);
+            } else {
+                setLocalUserRestrictionInternal(admin, key, enabled, userId);
+            }
+        } else if (ownerType == OWNER_TYPE_PROFILE_OWNER
+                || ownerType == OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE) {
+            if (UserRestrictionsUtils.isGlobal(OWNER_TYPE_PROFILE_OWNER, key)
+                    || (parent && ownerType == OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE
+                    && UserRestrictionsUtils.isGlobal(
+                    OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE, key))) {
+                setGlobalUserRestrictionInternal(admin, key, enabled);
+            } else {
+                int affectedUserId = parent
+                        ? getProfileParentId(userId) : userId;
+                setLocalUserRestrictionInternal(admin, key, enabled, affectedUserId);
+            }
+        } else {
+            throw new IllegalStateException("Non-DO/Non-PO cannot set restriction " + key
+                    + " while targetSdkVersion is less than UPSIDE_DOWN_CAKE");
         }
     }
 
@@ -18440,7 +18460,7 @@
             return;
         }
         new CalculateHasIncompatibleAccountsTask().executeOnExecutor(
-                calculateHasIncompatibleAccountsExecutor, null);
+                calculateHasIncompatibleAccountsExecutor);
     }
 
     @Nullable
@@ -22108,12 +22128,12 @@
     }
 
     @Override
-    public boolean isTheftDetectionTriggered(String callerPackageName) {
+    public boolean isDevicePotentiallyStolen(String callerPackageName) {
         final CallerIdentity caller = getCallerIdentity(callerPackageName);
         if (!android.app.admin.flags.Flags.deviceTheftImplEnabled()) {
             return false;
         }
-        enforcePermission(MANAGE_DEVICE_POLICY_THEFT_DETECTION, caller.getPackageName(),
+        enforcePermission(QUERY_DEVICE_STOLEN_STATE, caller.getPackageName(),
                 caller.getUserId());
 
         return mInjector.binderWithCleanCallingIdentity(() ->
@@ -23747,13 +23767,15 @@
         Preconditions.checkCallAuthorization(
                 hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
         return mInjector.binderWithCleanCallingIdentity(() -> {
-            boolean canForceMigration = forceMigration && !hasNonTestOnlyActiveAdmins();
-            if (!canForceMigration && !shouldMigrateV1ToDevicePolicyEngine()) {
-                return false;
+            synchronized (getLockObject()) {
+                boolean canForceMigration = forceMigration && !hasNonTestOnlyActiveAdmins();
+                if (!canForceMigration && !shouldMigrateV1ToDevicePolicyEngine()) {
+                    return false;
+                }
+                boolean migrated = migrateV1PoliciesToDevicePolicyEngine();
+                migrated &= migratePoliciesPostUpgradeToDevicePolicyEngineLocked();
+                return migrated;
             }
-            boolean migrated = migrateV1PoliciesToDevicePolicyEngine();
-            migrated &= migratePoliciesPostUpgradeToDevicePolicyEngineLocked();
-            return migrated;
         });
     }
 
@@ -23797,6 +23819,7 @@
         try {
             migrateScreenCapturePolicyLocked();
             migrateLockTaskPolicyLocked();
+            migrateUserRestrictionsLocked();
             return true;
         } catch (Exception e) {
             Slogf.e(LOG_TAG, e, "Error occurred during post upgrade migration to the device "
@@ -24066,6 +24089,42 @@
         });
     }
 
+    private void migrateUserRestrictionsLocked() {
+        Binder.withCleanCallingIdentity(() -> {
+            List<UserInfo> users = mUserManager.getUsers();
+            for (UserInfo userInfo : users) {
+                ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(userInfo.id);
+                if (admin == null) continue;
+                ComponentName adminComponent = admin.info.getComponent();
+                int userId = userInfo.id;
+                EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
+                        adminComponent,
+                        userId,
+                        admin);
+                int ownerType;
+                if (isDeviceOwner(admin)) {
+                    ownerType = OWNER_TYPE_DEVICE_OWNER;
+                } else if (isProfileOwnerOfOrganizationOwnedDevice(adminComponent, userId)) {
+                    ownerType = OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE;
+                } else if (isProfileOwner(adminComponent, userId)) {
+                    ownerType = OWNER_TYPE_PROFILE_OWNER;
+                } else {
+                    throw new IllegalStateException("Invalid DO/PO state");
+                }
+
+                for (final String restriction : admin.ensureUserRestrictions().keySet()) {
+                    setBackwardCompatibleUserRestrictionLocked(ownerType, enforcingAdmin, userId,
+                            restriction, /* enabled */ true, /* parent */ false);
+                }
+                for (final String restriction : admin.getParentActiveAdmin()
+                        .ensureUserRestrictions().keySet()) {
+                    setBackwardCompatibleUserRestrictionLocked(ownerType, enforcingAdmin, userId,
+                            restriction, /* enabled */ true, /* parent */ true);
+                }
+            }
+        });
+    }
+
     private List<PackageInfo> getInstalledPackagesOnUser(int userId) {
         return mInjector.binderWithCleanCallingIdentity(() ->
                 mContext.getPackageManager().getInstalledPackagesAsUser(
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 7c669f1..c6189ed 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -161,6 +161,7 @@
 import com.android.server.notification.NotificationManagerService;
 import com.android.server.oemlock.OemLockService;
 import com.android.server.om.OverlayManagerService;
+import com.android.server.ondeviceintelligence.OnDeviceIntelligenceManagerService;
 import com.android.server.os.BugreportManagerService;
 import com.android.server.os.DeviceIdentifiersPolicyService;
 import com.android.server.os.NativeTombstoneManagerService;
@@ -1965,6 +1966,7 @@
             startSystemCaptionsManagerService(context, t);
             startTextToSpeechManagerService(context, t);
             startWearableSensingService(t);
+            startOnDeviceIntelligenceService(t);
 
             if (deviceHasConfigString(
                     context, R.string.config_defaultAmbientContextDetectionService)) {
@@ -2166,16 +2168,14 @@
             }
             t.traceEnd();
 
-            if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)) {
-                t.traceBegin("StartVcnManagementService");
-                try {
-                    vcnManagement = VcnManagementService.create(context);
-                    ServiceManager.addService(Context.VCN_MANAGEMENT_SERVICE, vcnManagement);
-                } catch (Throwable e) {
-                    reportWtf("starting VCN Management Service", e);
-                }
-                t.traceEnd();
+            t.traceBegin("StartVcnManagementService");
+            try {
+                vcnManagement = VcnManagementService.create(context);
+                ServiceManager.addService(Context.VCN_MANAGEMENT_SERVICE, vcnManagement);
+            } catch (Throwable e) {
+                reportWtf("starting VCN Management Service", e);
             }
+            t.traceEnd();
 
             t.traceBegin("StartSystemUpdateManagerService");
             try {
@@ -3337,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..7618740 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -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
 
@@ -262,14 +268,33 @@
         } else {
             newFlags = newFlags andInv PermissionFlags.RESTRICTION_REVOKED
         }
-        newFlags =
-            if (
-                permission.isSoftRestricted && !isExempt &&
-                    !anyPackageInAppId(appId) {
-                        permissionName in it.androidPackage!!.requestedPermissions &&
-                            isSoftRestrictedPermissionExemptForPackage(it, permissionName)
+        val isSoftRestricted =
+            if (permission.isSoftRestricted && !isExempt) {
+                val targetSdkVersion =
+                    reducePackageInAppId(appId, Build.VERSION_CODES.CUR_DEVELOPMENT) {
+                        targetSdkVersion,
+                        packageState ->
+                        if (permissionName in packageState.androidPackage!!.requestedPermissions) {
+                            targetSdkVersion.coerceAtMost(
+                                packageState.androidPackage!!.targetSdkVersion
+                            )
+                        } else {
+                            targetSdkVersion
+                        }
                     }
-            ) {
+                !anyPackageInAppId(appId) {
+                    permissionName in it.androidPackage!!.requestedPermissions &&
+                        isSoftRestrictedPermissionExemptForPackage(
+                            it,
+                            targetSdkVersion,
+                            permissionName
+                        )
+                }
+            } else {
+                false
+            }
+        newFlags =
+            if (isSoftRestricted) {
                 newFlags or PermissionFlags.SOFT_RESTRICTED
             } else {
                 newFlags andInv PermissionFlags.SOFT_RESTRICTED
@@ -1153,9 +1178,14 @@
                 }
             newFlags =
                 if (
-                    permission.isSoftRestricted && !isExempt &&
+                    permission.isSoftRestricted &&
+                        !isExempt &&
                         !requestingPackageStates.anyIndexed { _, it ->
-                            isSoftRestrictedPermissionExemptForPackage(it, permissionName)
+                            isSoftRestrictedPermissionExemptForPackage(
+                                it,
+                                targetSdkVersion,
+                                permissionName
+                            )
                         }
                 ) {
                     newFlags or PermissionFlags.SOFT_RESTRICTED
@@ -1274,7 +1304,7 @@
                     SigningDetails.CertCapabilities.PERMISSION
                 )
         if (!Flags.signaturePermissionAllowlistEnabled()) {
-            return hasCommonSigner;
+            return hasCommonSigner
         }
         if (!hasCommonSigner) {
             return false
@@ -1308,7 +1338,7 @@
                         " ${packageState.packageName} (${packageState.path}) not in" +
                         " signature permission allowlist"
                 )
-                if (!Build.isDebuggable()) {
+                if (!Build.isDebuggable() || isSignaturePermissionAllowlistForceEnforced) {
                     return false
                 }
             }
@@ -1438,13 +1468,20 @@
     }
 
     // See also SoftRestrictedPermissionPolicy.mayGrantPermission()
+    // Note: we need the appIdTargetSdkVersion parameter here because we are OR-ing the exempt
+    // status for all packages in a shared UID, but the storage soft restriction logic needs to NOT
+    // exempt when the target SDK version is low, which is the opposite of what most of our code do,
+    // and thus can't check the individual package's target SDK version and rely on the OR among
+    // them.
     private fun isSoftRestrictedPermissionExemptForPackage(
         packageState: PackageState,
+        appIdTargetSdkVersion: Int,
         permissionName: String
     ): Boolean =
         when (permissionName) {
-            Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE ->
-                packageState.androidPackage!!.targetSdkVersion >= Build.VERSION_CODES.Q
+            Manifest.permission.READ_EXTERNAL_STORAGE,
+            Manifest.permission.WRITE_EXTERNAL_STORAGE ->
+                appIdTargetSdkVersion >= Build.VERSION_CODES.Q
             else -> false
         }
 
diff --git a/services/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/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/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 2867041..35b69f8 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -54,6 +54,7 @@
 
 import com.android.internal.R;
 import com.android.server.display.config.HdrBrightnessData;
+import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint;
 import com.android.server.display.config.ThermalStatus;
 import com.android.server.display.feature.DisplayManagerFlags;
 
@@ -108,6 +109,7 @@
         when(mContext.getResources()).thenReturn(mResources);
         when(mFlags.areAutoBrightnessModesEnabled()).thenReturn(true);
         when(mFlags.isSensorBasedBrightnessThrottlingEnabled()).thenReturn(true);
+        when(mFlags.isIdleScreenRefreshRateTimeoutEnabled()).thenReturn(true);
         mockDeviceConfigs();
     }
 
@@ -146,6 +148,8 @@
         assertNull(mDisplayDeviceConfig.getProximitySensor().type);
         assertNull(mDisplayDeviceConfig.getProximitySensor().name);
         assertEquals(TEMPERATURE_TYPE_SKIN, mDisplayDeviceConfig.getTempSensor().type);
+        assertEquals(List.of(), mDisplayDeviceConfig
+                .getIdleScreenRefreshRateTimeoutLuxThresholdPoint());
         assertNull(mDisplayDeviceConfig.getTempSensor().name);
         assertTrue(mDisplayDeviceConfig.isAutoBrightnessAvailable());
     }
@@ -226,6 +230,19 @@
         assertNotNull(mDisplayDeviceConfig.getHostUsiVersion());
         assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMajorVersion(), 2);
         assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMinorVersion(), 0);
+
+        List<IdleScreenRefreshRateTimeoutLuxThresholdPoint>
+                idleScreenRefreshRateTimeoutLuxThresholdPoints =
+                mDisplayDeviceConfig.getIdleScreenRefreshRateTimeoutLuxThresholdPoint();
+        assertEquals(2, idleScreenRefreshRateTimeoutLuxThresholdPoints.size());
+        assertEquals(6, idleScreenRefreshRateTimeoutLuxThresholdPoints.get(0).getLux()
+                .intValue());
+        assertEquals(1000, idleScreenRefreshRateTimeoutLuxThresholdPoints.get(0)
+                .getTimeout().intValue());
+        assertEquals(10, idleScreenRefreshRateTimeoutLuxThresholdPoints.get(1)
+                .getLux().intValue());
+        assertEquals(800, idleScreenRefreshRateTimeoutLuxThresholdPoints.get(1)
+                .getTimeout().intValue());
     }
 
     @Test
@@ -734,6 +751,8 @@
 
         assertEquals(brightnessIntToFloat(35),
                 mDisplayDeviceConfig.getBrightnessCapForWearBedtimeMode(), ZERO_DELTA);
+        assertEquals(List.of(), mDisplayDeviceConfig
+                .getIdleScreenRefreshRateTimeoutLuxThresholdPoint());
     }
 
     @Test
@@ -1587,6 +1606,18 @@
                 +   "<screenBrightnessCapForWearBedtimeMode>"
                 +       "0.1"
                 +   "</screenBrightnessCapForWearBedtimeMode>"
+                +   "<idleScreenRefreshRateTimeout>"
+                +       "<luxThresholds>"
+                +           "<point>"
+                +               "<lux>6</lux>"
+                +               "<timeout>1000</timeout>"
+                +           "</point>"
+                +           "<point>"
+                +               "<lux>10</lux>"
+                +               "<timeout>800</timeout>"
+                +           "</point>"
+                +       "</luxThresholds>"
+                +   "</idleScreenRefreshRateTimeout>"
                 + "</displayConfiguration>\n";
     }
 
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..869cec8 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -78,6 +78,7 @@
 import android.graphics.Rect;
 import android.hardware.Sensor;
 import android.hardware.SensorManager;
+import android.hardware.devicestate.DeviceState;
 import android.hardware.display.BrightnessConfiguration;
 import android.hardware.display.BrightnessInfo;
 import android.hardware.display.Curve;
@@ -106,6 +107,7 @@
 import android.os.UserManager;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
+import android.test.mock.MockContentResolver;
 import android.util.SparseArray;
 import android.view.ContentRecordingSession;
 import android.view.Display;
@@ -123,6 +125,8 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.R;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
 import com.android.modules.utils.testing.ExtendedMockitoRule;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
@@ -204,6 +208,8 @@
     @Rule
     public SetFlagsRule mSetFlagsRule =
             new SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT);
+    @Rule
+    public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
 
     private Context mContext;
 
@@ -376,6 +382,8 @@
         when(display.getBrightnessInfo()).thenReturn(mock(BrightnessInfo.class));
         mContext = spy(new ContextWrapper(
                 ApplicationProvider.getApplicationContext().createDisplayContext(display)));
+        final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
+        when(mContext.getContentResolver()).thenReturn(resolver);
         mResources = Mockito.spy(mContext.getResources());
         mPowerHandler = new Handler(Looper.getMainLooper());
         manageDisplaysPermission(/* granted= */ false);
@@ -714,7 +722,9 @@
         IDisplayManagerCallback displayChangesCallback = registerDisplayChangeCallback(
                 displayManager);
 
-        listener.onStateChanged(123);
+        listener.onDeviceStateChanged(new DeviceState(
+                new DeviceState.Configuration.Builder(123 /* identifier */,
+                        "TEST" /* name */).build()));
         waitForIdleHandler(handler);
 
         InOrder inOrder = inOrder(mMockWindowManagerInternal, displayChangesCallback);
@@ -2408,6 +2418,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 +2451,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 +2499,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 +2665,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 +2713,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/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 2939192..d0c7077 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -650,7 +650,6 @@
     public void testDeviceShouldBePutToSleep() {
         assertTrue(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
                 DEVICE_STATE_OPEN,
-                /* isOverrideActive= */false,
                 /* isInteractive= */true,
                 /* isBootCompleted= */true));
     }
@@ -661,7 +660,6 @@
 
         assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
                 DEVICE_STATE_OPEN,
-                /* isOverrideActive= */false,
                 /* isInteractive= */true,
                 /* isBootCompleted= */true));
     }
@@ -670,21 +668,10 @@
     public void testDeviceShouldNotBePutToSleep() {
         assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_OPEN,
                 DEVICE_STATE_CLOSED,
-                /* isOverrideActive= */false,
                 /* isInteractive= */true,
                 /* isBootCompleted= */true));
         assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
                 INVALID_DEVICE_STATE_IDENTIFIER,
-                /* isOverrideActive= */false,
-                /* isInteractive= */true,
-                /* isBootCompleted= */true));
-    }
-
-    @Test
-    public void testDeviceShouldNotBePutToSleepDifferentBaseState() {
-        assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
-                DEVICE_STATE_OPEN,
-                /* isOverrideActive= */true,
                 /* isInteractive= */true,
                 /* isBootCompleted= */true));
     }
@@ -750,7 +737,7 @@
         // We can only have one default display
         assertEquals(DEFAULT_DISPLAY, id(display1));
 
-        mLogicalDisplayMapper.setDeviceStateLocked(0, false);
+        mLogicalDisplayMapper.setDeviceStateLocked(0);
         advanceTime(1000);
         // The new state is not applied until the boot is completed
         assertTrue(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
@@ -771,7 +758,7 @@
         assertEquals("concurrent", mLogicalDisplayMapper.getDisplayLocked(device2)
                 .getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
 
-        mLogicalDisplayMapper.setDeviceStateLocked(1, false);
+        mLogicalDisplayMapper.setDeviceStateLocked(1);
         advanceTime(1000);
         assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
         assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
@@ -784,7 +771,7 @@
                 mLogicalDisplayMapper.getDisplayLocked(device2)
                         .getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
 
-        mLogicalDisplayMapper.setDeviceStateLocked(2, false);
+        mLogicalDisplayMapper.setDeviceStateLocked(2);
         advanceTime(1000);
         assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
         assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
@@ -861,7 +848,7 @@
         // 3) Send DISPLAY_DEVICE_EVENT_CHANGE to inform the mapper of the new display state
         // 4) Dispatch handler events.
         mLogicalDisplayMapper.onBootCompleted();
-        mLogicalDisplayMapper.setDeviceStateLocked(0, false);
+        mLogicalDisplayMapper.setDeviceStateLocked(0);
         mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED);
         advanceTime(1000);
         final int[] allDisplayIds = mLogicalDisplayMapper.getDisplayIdsLocked(
@@ -891,7 +878,7 @@
                 /* includeDisabled= */ false));
 
         // Now do it again to go back to state 1
-        mLogicalDisplayMapper.setDeviceStateLocked(1, false);
+        mLogicalDisplayMapper.setDeviceStateLocked(1);
         mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED);
         advanceTime(1000);
         final int[] threeDisplaysEnabled = mLogicalDisplayMapper.getDisplayIdsLocked(
@@ -945,7 +932,7 @@
         // We can only have one default display
         assertEquals(DEFAULT_DISPLAY, id(display1));
 
-        mLogicalDisplayMapper.setDeviceStateLocked(0, false);
+        mLogicalDisplayMapper.setDeviceStateLocked(0);
         advanceTime(1000);
         mLogicalDisplayMapper.onBootCompleted();
         advanceTime(1000);
@@ -964,11 +951,11 @@
     /////////////////
 
     private void finishBootAndFoldDevice() {
-        mLogicalDisplayMapper.setDeviceStateLocked(DEVICE_STATE_OPEN, false);
+        mLogicalDisplayMapper.setDeviceStateLocked(DEVICE_STATE_OPEN);
         advanceTime(1000);
         mLogicalDisplayMapper.onBootCompleted();
         advanceTime(1000);
-        mLogicalDisplayMapper.setDeviceStateLocked(DEVICE_STATE_CLOSED, false);
+        mLogicalDisplayMapper.setDeviceStateLocked(DEVICE_STATE_CLOSED);
         advanceTime(1000);
     }
 
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 549f0d7..e798aa2 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
@@ -107,8 +107,7 @@
 
     @Test
     public void testLetterbox() {
-        mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
-                /*isAnisotropyCorrectionEnabled=*/ false);
+        mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice);
         mDisplayDeviceInfo.xDpi = 0.5f;
         mDisplayDeviceInfo.yDpi = 1.0f;
 
@@ -146,7 +145,8 @@
     @Test
     public void testNoLetterbox_anisotropyCorrection() {
         mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
-                /*isAnisotropyCorrectionEnabled=*/ true);
+                /*isAnisotropyCorrectionEnabled=*/ true,
+                /*isAlwaysRotateDisplayDeviceEnabled=*/ 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
@@ -173,7 +173,8 @@
     @Test
     public void testLetterbox_anisotropyCorrectionYDpi() {
         mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
-                /*isAnisotropyCorrectionEnabled=*/ true);
+                /*isAnisotropyCorrectionEnabled=*/ true,
+                /*isAlwaysRotateDisplayDeviceEnabled=*/ true);
 
         DisplayInfo displayInfo = new DisplayInfo();
         displayInfo.logicalWidth = DISPLAY_WIDTH;
@@ -191,8 +192,7 @@
 
     @Test
     public void testPillarbox() {
-        mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
-                /*isAnisotropyCorrectionEnabled=*/ false);
+        mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice);
         mDisplayDeviceInfo.xDpi = 0.5f;
         mDisplayDeviceInfo.yDpi = 1.0f;
 
@@ -230,7 +230,8 @@
     @Test
     public void testPillarbox_anisotropyCorrection() {
         mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
-                /*isAnisotropyCorrectionEnabled=*/ true);
+                /*isAnisotropyCorrectionEnabled=*/ true,
+                /*isAlwaysRotateDisplayDeviceEnabled=*/ true);
 
         DisplayInfo displayInfo = new DisplayInfo();
         displayInfo.logicalWidth = DISPLAY_WIDTH;
@@ -257,7 +258,8 @@
     @Test
     public void testNoPillarbox_anisotropyCorrectionYDpi() {
         mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
-                /*isAnisotropyCorrectionEnabled=*/ true);
+                /*isAnisotropyCorrectionEnabled=*/ true,
+                /*isAlwaysRotateDisplayDeviceEnabled=*/ 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
@@ -315,6 +317,47 @@
     }
 
     @Test
+    public void testGetDisplayPositionAlwaysRotateDisplayEnabled() {
+        mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+                /*isAnisotropyCorrectionEnabled=*/ true,
+                /*isAlwaysRotateDisplayDeviceEnabled=*/ true);
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        Point expectedPosition = new Point();
+
+        SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+        assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition());
+
+        expectedPosition.set(20, 40);
+        mLogicalDisplay.setDisplayOffsetsLocked(20, 40);
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+        assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition());
+
+        DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.logicalWidth = DISPLAY_WIDTH;
+        displayInfo.logicalHeight = DISPLAY_HEIGHT;
+        // Rotation sent from WindowManager is always taken into account by LogicalDisplay
+        // not matter whether FLAG_ROTATES_WITH_CONTENT is set or not.
+        // This is because WindowManager takes care of rotation and expects that LogicalDisplay
+        // will follow the rotation supplied by WindowManager
+        expectedPosition.set(115, -20);
+        displayInfo.rotation = Surface.ROTATION_90;
+        mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+        assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition());
+
+        expectedPosition.set(40, -20);
+        mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        displayInfo.logicalWidth = DISPLAY_HEIGHT;
+        displayInfo.logicalHeight = DISPLAY_WIDTH;
+        displayInfo.rotation = Surface.ROTATION_90;
+        mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+        assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition());
+    }
+
+    @Test
     public void testDisplayInputFlags() {
         DisplayDevice displayDevice = new DisplayDevice(mDisplayAdapter, mDisplayToken,
                 "unique_display_id", mContext) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt
index 638924e..b182cce 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt
@@ -95,9 +95,9 @@
     ) {
         ALL_ENABLED(true, true, CombinedVote(
                 listOf(DisableRefreshRateSwitchingVote(true),
-                        SupportedModesVote(
-                                listOf(SupportedModesVote.SupportedMode(60f, 60f),
-                                        SupportedModesVote.SupportedMode(120f, 120f)))))),
+                        SupportedRefreshRatesVote(
+                                listOf(SupportedRefreshRatesVote.RefreshRates(60f, 60f),
+                                        SupportedRefreshRatesVote.RefreshRates(120f, 120f)))))),
         VRR_NOT_SUPPORTED(false, true, DisableRefreshRateSwitchingVote(true)),
         VSYNC_VOTE_DISABLED(true, false, DisableRefreshRateSwitchingVote(true))
     }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/ProximitySensorObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/ProximitySensorObserverTest.java
new file mode 100644
index 0000000..e93e5bc
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/ProximitySensorObserverTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.mode;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.hardware.display.DisplayManagerInternal;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.SurfaceControl;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.sensors.SensorManagerInternal;
+
+import junitparams.JUnitParamsRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(JUnitParamsRunner.class)
+public class ProximitySensorObserverTest {
+
+    private static final float FLOAT_TOLERANCE = 0.01f;
+    private static final int DISPLAY_ID = 1;
+    private static final SurfaceControl.RefreshRateRange REFRESH_RATE_RANGE =
+            new SurfaceControl.RefreshRateRange(60, 90);
+
+    private final VotesStorage mStorage = new VotesStorage(() -> { }, null);
+    private final FakesInjector mInjector = new FakesInjector();
+    private ProximitySensorObserver mSensorObserver;
+
+    @Mock
+    DisplayManagerInternal mMockDisplayManagerInternal;
+    @Mock
+    SensorManagerInternal mMockSensorManagerInternal;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mMockDisplayManagerInternal.getRefreshRateForDisplayAndSensor(eq(DISPLAY_ID),
+                any(), any())).thenReturn(REFRESH_RATE_RANGE);
+        mSensorObserver = new ProximitySensorObserver(mStorage, mInjector);
+        mSensorObserver.observe();
+    }
+
+    @Test
+    public void testAddsProximityVoteIfSensorManagerProximityActive() {
+        mSensorObserver.onProximityActive(true);
+
+        SparseArray<Vote> displayVotes = mStorage.getVotes(DISPLAY_ID);
+        assertThat(displayVotes.size()).isEqualTo(1);
+        Vote vote = displayVotes.get(Vote.PRIORITY_PROXIMITY);
+        assertThat(vote).isNotNull();
+        assertThat(vote).isInstanceOf(CombinedVote.class);
+        CombinedVote combinedVote = (CombinedVote) vote;
+        RefreshRateVote.PhysicalVote physicalVote =
+                (RefreshRateVote.PhysicalVote) combinedVote.mVotes.get(0);
+        assertThat(physicalVote.mMinRefreshRate).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(physicalVote.mMaxRefreshRate).isWithin(FLOAT_TOLERANCE).of(90);
+    }
+
+    @Test
+    public void testDoesNotAddProximityVoteIfSensorManagerProximityNotActive() {
+        mSensorObserver.onProximityActive(false);
+
+        SparseArray<Vote> displayVotes = mStorage.getVotes(DISPLAY_ID);
+        assertThat(displayVotes.size()).isEqualTo(0);
+    }
+
+    @Test
+    public void testDoesNotAddProximityVoteIfDoze() {
+        mInjector.mDozeState = true;
+        mSensorObserver.onDisplayChanged(DISPLAY_ID);
+        mSensorObserver.onProximityActive(true);
+
+        SparseArray<Vote> displayVotes = mStorage.getVotes(DISPLAY_ID);
+        assertThat(displayVotes.size()).isEqualTo(0);
+    }
+
+    private class FakesInjector extends DisplayModeDirectorTest.FakesInjector {
+
+        private boolean mDozeState = false;
+
+        @Override
+        public Display[] getDisplays() {
+            return new Display[] { createDisplay(DISPLAY_ID) };
+        }
+
+        @Override
+        public DisplayManagerInternal getDisplayManagerInternal() {
+            return mMockDisplayManagerInternal;
+        }
+
+        @Override
+        public SensorManagerInternal getSensorManagerInternal() {
+            return mMockSensorManagerInternal;
+        }
+
+        @Override
+        public boolean isDozeState(Display d) {
+            return mDozeState;
+        }
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
index ebb4f18..230317b 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
@@ -85,9 +85,9 @@
             internal val expectedVote: Vote?
     ) {
         ALL_ENABLED(true, true, true,
-                SupportedModesVote(listOf(
-                        SupportedModesVote.SupportedMode(60f, 240f),
-                        SupportedModesVote.SupportedMode(60f, 60f)
+                SupportedRefreshRatesVote(listOf(
+                        SupportedRefreshRatesVote.RefreshRates(60f, 240f),
+                        SupportedRefreshRatesVote.RefreshRates(60f, 60f)
                 ))),
         LOW_POWER_OFF(true, true, false, null),
         DVRR_NOT_SUPPORTED_LOW_POWER_ON(false, true, true,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedModesVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedModesVoteTest.kt
index 04e6265..6ce49b8 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedModesVoteTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedModesVoteTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 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.
@@ -27,12 +27,9 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class SupportedModesVoteTest {
-    private val supportedModes = listOf(
-            SupportedModesVote.SupportedMode(60f, 90f ),
-            SupportedModesVote.SupportedMode(120f, 240f )
-    )
+    private val supportedModes = listOf(1, 2, 4)
 
-    private val otherMode = SupportedModesVote.SupportedMode(120f, 120f )
+    private val otherMode = 5
 
     private lateinit var supportedModesVote: SupportedModesVote
 
@@ -42,31 +39,31 @@
     }
 
     @Test
-    fun `adds supported modes if supportedModes in summary is null`() {
+    fun `adds supported mode ids if supportedModeIds in summary is null`() {
         val summary = createVotesSummary()
 
         supportedModesVote.updateSummary(summary)
 
-        assertThat(summary.supportedModes).containsExactlyElementsIn(supportedModes)
+        assertThat(summary.supportedModeIds).containsExactlyElementsIn(supportedModes)
     }
 
     @Test
-    fun `does not add supported modes if summary has empty list of modes`() {
+    fun `does not add supported mode ids if summary has empty list of modeIds`() {
         val summary = createVotesSummary()
-        summary.supportedModes = ArrayList()
+        summary.supportedModeIds = ArrayList()
 
         supportedModesVote.updateSummary(summary)
 
-        assertThat(summary.supportedModes).isEmpty()
+        assertThat(summary.supportedModeIds).isEmpty()
     }
 
     @Test
     fun `filters out modes that does not match vote`() {
         val summary = createVotesSummary()
-        summary.supportedModes = ArrayList(listOf(otherMode, supportedModes[0]))
+        summary.supportedModeIds = ArrayList(listOf(otherMode, supportedModes[0]))
 
         supportedModesVote.updateSummary(summary)
 
-        assertThat(summary.supportedModes).containsExactly(supportedModes[0])
+        assertThat(summary.supportedModeIds).containsExactly(supportedModes[0])
     }
 }
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedRefreshRatesVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedRefreshRatesVoteTest.kt
new file mode 100644
index 0000000..d0c112b
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedRefreshRatesVoteTest.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 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.server.display.mode
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SupportedRefreshRatesVoteTest {
+    private val refreshRates = listOf(
+            SupportedRefreshRatesVote.RefreshRates(60f, 90f),
+            SupportedRefreshRatesVote.RefreshRates(120f, 240f)
+    )
+
+    private val otherMode = SupportedRefreshRatesVote.RefreshRates(120f, 120f)
+
+    private lateinit var supportedRefreshRatesVote: SupportedRefreshRatesVote
+
+    @Before
+    fun setUp() {
+        supportedRefreshRatesVote = SupportedRefreshRatesVote(refreshRates)
+    }
+
+    @Test
+    fun `adds supported refresh rates if supportedModes in summary is null`() {
+        val summary = createVotesSummary()
+
+        supportedRefreshRatesVote.updateSummary(summary)
+
+        assertThat(summary.supportedRefreshRates).containsExactlyElementsIn(refreshRates)
+    }
+
+    @Test
+    fun `does not add supported refresh rates if summary has empty list of refresh rates`() {
+        val summary = createVotesSummary()
+        summary.supportedRefreshRates = ArrayList()
+
+        supportedRefreshRatesVote.updateSummary(summary)
+
+        assertThat(summary.supportedRefreshRates).isEmpty()
+    }
+
+    @Test
+    fun `filters out supported refresh rates that does not match vote`() {
+        val summary = createVotesSummary()
+        summary.supportedRefreshRates = ArrayList(listOf(otherMode, refreshRates[0]))
+
+        supportedRefreshRatesVote.updateSummary(summary)
+
+        assertThat(summary.supportedRefreshRates).containsExactly(refreshRates[0])
+    }
+}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SystemRequestObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SystemRequestObserverTest.kt
new file mode 100644
index 0000000..c49205b
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SystemRequestObserverTest.kt
@@ -0,0 +1,212 @@
+/*
+ * 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.mode
+
+import android.os.IBinder
+import android.os.RemoteException
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import com.google.testing.junit.testparameterinjector.TestParameterInjector
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doThrow
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+private const val DISPLAY_ID = 1
+private const val DISPLAY_ID_OTHER = 2
+
+@SmallTest
+@RunWith(TestParameterInjector::class)
+class SystemRequestObserverTest {
+
+
+    @get:Rule
+    val mockitoRule = MockitoJUnit.rule()
+
+    private val mockToken = mock<IBinder>()
+    private val mockOtherToken = mock<IBinder>()
+
+    private val storage = VotesStorage({}, null)
+
+    @Test
+    fun `requestDisplayModes adds vote to storage`() {
+        val systemRequestObserver = SystemRequestObserver(storage)
+        val requestedModes = intArrayOf(1, 2, 3)
+
+        systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes)
+
+        val votes = storage.getVotes(DISPLAY_ID)
+        assertThat(votes.size()).isEqualTo(1)
+        val vote = votes.get(Vote.PRIORITY_SYSTEM_REQUESTED_MODES)
+        assertThat(vote).isInstanceOf(SupportedModesVote::class.java)
+        val supportedModesVote = vote as SupportedModesVote
+        assertThat(supportedModesVote.mModeIds.size).isEqualTo(requestedModes.size)
+        for (mode in requestedModes) {
+            assertThat(supportedModesVote.mModeIds).contains(mode)
+        }
+    }
+
+    @Test
+    fun `requestDisplayModes overrides votes in storage`() {
+        val systemRequestObserver = SystemRequestObserver(storage)
+
+        systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, intArrayOf(1, 2, 3))
+
+        val overrideModes = intArrayOf(10, 20, 30)
+        systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, overrideModes)
+
+        val votes = storage.getVotes(DISPLAY_ID)
+        assertThat(votes.size()).isEqualTo(1)
+        val vote = votes.get(Vote.PRIORITY_SYSTEM_REQUESTED_MODES)
+        assertThat(vote).isInstanceOf(SupportedModesVote::class.java)
+        val supportedModesVote = vote as SupportedModesVote
+        assertThat(supportedModesVote.mModeIds.size).isEqualTo(overrideModes.size)
+        for (mode in overrideModes) {
+            assertThat(supportedModesVote.mModeIds).contains(mode)
+        }
+    }
+
+    @Test
+    fun `requestDisplayModes removes vote to storage`() {
+        val systemRequestObserver = SystemRequestObserver(storage)
+        val requestedModes = intArrayOf(1, 2, 3)
+
+        systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes)
+        systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, null)
+
+        val votes = storage.getVotes(DISPLAY_ID)
+        assertThat(votes.size()).isEqualTo(0)
+    }
+
+    @Test
+    fun `requestDisplayModes calls linkToDeath to token`() {
+        val systemRequestObserver = SystemRequestObserver(storage)
+        val requestedModes = intArrayOf(1, 2, 3)
+
+        systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes)
+
+        verify(mockToken).linkToDeath(any(), eq(0))
+    }
+
+    @Test
+    fun `does not add votes to storage if binder died when requestDisplayModes called`() {
+        val systemRequestObserver = SystemRequestObserver(storage)
+        val requestedModes = intArrayOf(1, 2, 3)
+
+        doThrow(RemoteException()).whenever(mockOtherToken).linkToDeath(any(), eq(0))
+        systemRequestObserver.requestDisplayModes(mockOtherToken, DISPLAY_ID, requestedModes)
+
+        val votes = storage.getVotes(DISPLAY_ID)
+        assertThat(votes.size()).isEqualTo(0)
+    }
+
+    @Test
+    fun `removes all votes from storage when binder dies`() {
+        val systemRequestObserver = SystemRequestObserver(storage)
+        val requestedModes = intArrayOf(1, 2, 3)
+
+        systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes)
+        val deathRecipientCaptor = argumentCaptor<IBinder.DeathRecipient>()
+        verify(mockToken).linkToDeath(deathRecipientCaptor.capture(), eq(0))
+
+        deathRecipientCaptor.lastValue.binderDied(mockToken)
+
+        val votes = storage.getVotes(DISPLAY_ID)
+        assertThat(votes.size()).isEqualTo(0)
+    }
+
+    @Test
+    fun `calls unlinkToDeath on token when no votes remaining`() {
+        val systemRequestObserver = SystemRequestObserver(storage)
+        val requestedModes = intArrayOf(1, 2, 3)
+
+        systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes)
+        systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, null)
+
+        verify(mockToken).unlinkToDeath(any(), eq(0))
+    }
+
+    @Test
+    fun `does not call unlinkToDeath on token when votes for other display in storage`() {
+        val systemRequestObserver = SystemRequestObserver(storage)
+        val requestedModes = intArrayOf(1, 2, 3)
+
+        systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes)
+        systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID_OTHER, requestedModes)
+        systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, null)
+
+        verify(mockToken, never()).unlinkToDeath(any(), eq(0))
+    }
+
+    @Test
+    fun `requestDisplayModes subset modes from different tokens`() {
+        val systemRequestObserver = SystemRequestObserver(storage)
+        val requestedModes = intArrayOf(1, 2, 3)
+        systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes)
+
+        val requestedOtherModes = intArrayOf(2, 3, 4)
+        systemRequestObserver.requestDisplayModes(mockOtherToken, DISPLAY_ID, requestedOtherModes)
+
+        verify(mockToken).linkToDeath(any(), eq(0))
+        verify(mockOtherToken).linkToDeath(any(), eq(0))
+        verify(mockToken, never()).unlinkToDeath(any(), eq(0))
+        verify(mockOtherToken, never()).unlinkToDeath(any(), eq(0))
+
+        val expectedModes = intArrayOf(2, 3)
+        val votes = storage.getVotes(DISPLAY_ID)
+        assertThat(votes.size()).isEqualTo(1)
+        val vote = votes.get(Vote.PRIORITY_SYSTEM_REQUESTED_MODES)
+        assertThat(vote).isInstanceOf(SupportedModesVote::class.java)
+        val supportedModesVote = vote as SupportedModesVote
+        assertThat(supportedModesVote.mModeIds.size).isEqualTo(expectedModes.size)
+        for (mode in expectedModes) {
+            assertThat(supportedModesVote.mModeIds).contains(mode)
+        }
+    }
+
+    @Test
+    fun `recalculates vote if one binder dies`() {
+        val systemRequestObserver = SystemRequestObserver(storage)
+        val requestedModes = intArrayOf(1, 2, 3)
+        systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes)
+
+        val requestedOtherModes = intArrayOf(2, 3, 4)
+        systemRequestObserver.requestDisplayModes(mockOtherToken, DISPLAY_ID, requestedOtherModes)
+
+        val deathRecipientCaptor = argumentCaptor<IBinder.DeathRecipient>()
+        verify(mockOtherToken).linkToDeath(deathRecipientCaptor.capture(), eq(0))
+        deathRecipientCaptor.lastValue.binderDied(mockOtherToken)
+
+        val votes = storage.getVotes(DISPLAY_ID)
+        assertThat(votes.size()).isEqualTo(1)
+        val vote = votes.get(Vote.PRIORITY_SYSTEM_REQUESTED_MODES)
+        assertThat(vote).isInstanceOf(SupportedModesVote::class.java)
+        val supportedModesVote = vote as SupportedModesVote
+        assertThat(supportedModesVote.mModeIds.size).isEqualTo(requestedModes.size)
+        for (mode in requestedModes) {
+            assertThat(supportedModesVote.mModeIds).contains(mode)
+        }
+    }
+}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt
index 910e03c..6b90bde 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt
@@ -18,10 +18,10 @@
 
 internal fun createVotesSummary(
         isDisplayResolutionRangeVotingEnabled: Boolean = true,
-        vsyncProximityVoteEnabled: Boolean = true,
+        supportedModesVoteEnabled: Boolean = true,
         loggingEnabled: Boolean = true,
         supportsFrameRateOverride: Boolean = true
 ): VoteSummary {
-    return VoteSummary(isDisplayResolutionRangeVotingEnabled, vsyncProximityVoteEnabled,
+    return VoteSummary(isDisplayResolutionRangeVotingEnabled, supportedModesVoteEnabled,
             loggingEnabled, supportsFrameRateOverride)
 }
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt
index d6c8469..04b35f1 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt
@@ -28,29 +28,29 @@
 @RunWith(TestParameterInjector::class)
 class VoteSummaryTest {
 
-    enum class SupportedModesVoteTestCase(
-            val vsyncProximityVoteEnabled: Boolean,
-            internal val summarySupportedModes: List<SupportedModesVote.SupportedMode>?,
+    enum class SupportedRefreshRatesTestCase(
+            val supportedModesVoteEnabled: Boolean,
+            internal val summaryRefreshRates: List<SupportedRefreshRatesVote.RefreshRates>?,
             val modesToFilter: Array<Display.Mode>,
             val expectedModeIds: List<Int>
     ) {
         HAS_NO_MATCHING_VOTE(true,
-                listOf(SupportedModesVote.SupportedMode(60f, 60f)),
+                listOf(SupportedRefreshRatesVote.RefreshRates(60f, 60f)),
                 arrayOf(createMode(1, 90f, 90f),
                         createMode(2, 90f, 60f),
                         createMode(3, 60f, 90f)),
                 listOf()
         ),
         HAS_SINGLE_MATCHING_VOTE(true,
-                listOf(SupportedModesVote.SupportedMode(60f, 90f)),
+                listOf(SupportedRefreshRatesVote.RefreshRates(60f, 90f)),
                 arrayOf(createMode(1, 90f, 90f),
                         createMode(2, 90f, 60f),
                         createMode(3, 60f, 90f)),
                 listOf(3)
         ),
         HAS_MULTIPLE_MATCHING_VOTES(true,
-                listOf(SupportedModesVote.SupportedMode(60f, 90f),
-                        SupportedModesVote.SupportedMode(90f, 90f)),
+                listOf(SupportedRefreshRatesVote.RefreshRates(60f, 90f),
+                        SupportedRefreshRatesVote.RefreshRates(90f, 90f)),
                 arrayOf(createMode(1, 90f, 90f),
                         createMode(2, 90f, 60f),
                         createMode(3, 60f, 90f)),
@@ -70,7 +70,69 @@
                         createMode(3, 60f, 90f)),
                 listOf(1, 2, 3)
         ),
-        HAS_VSYNC_PROXIMITY_DISABLED(false,
+        HAS_SUPPORTED_MODES_VOTE_DISABLED(false,
+                listOf(),
+                arrayOf(createMode(1, 90f, 90f),
+                        createMode(2, 90f, 60f),
+                        createMode(3, 60f, 90f)),
+                listOf(1, 2, 3)
+        ),
+    }
+
+    @Test
+    fun `filters modes for summary supportedRefreshRates`(
+            @TestParameter testCase: SupportedRefreshRatesTestCase
+    ) {
+        val summary = createSummary(testCase.supportedModesVoteEnabled)
+        summary.supportedRefreshRates = testCase.summaryRefreshRates
+
+        val result = summary.filterModes(testCase.modesToFilter)
+
+        assertThat(result.map { it.modeId }).containsExactlyElementsIn(testCase.expectedModeIds)
+    }
+
+    enum class SupportedModesTestCase(
+            val supportedModesVoteEnabled: Boolean,
+            internal val summarySupportedModes: List<Int>?,
+            val modesToFilter: Array<Display.Mode>,
+            val expectedModeIds: List<Int>
+    ) {
+        HAS_NO_MATCHING_VOTE(true,
+                listOf(4, 5),
+                arrayOf(createMode(1, 90f, 90f),
+                        createMode(2, 90f, 60f),
+                        createMode(3, 60f, 90f)),
+                listOf()
+        ),
+        HAS_SINGLE_MATCHING_VOTE(true,
+                listOf(3),
+                arrayOf(createMode(1, 90f, 90f),
+                        createMode(2, 90f, 60f),
+                        createMode(3, 60f, 90f)),
+                listOf(3)
+        ),
+        HAS_MULTIPLE_MATCHING_VOTES(true,
+                listOf(1, 3),
+                arrayOf(createMode(1, 90f, 90f),
+                        createMode(2, 90f, 60f),
+                        createMode(3, 60f, 90f)),
+                listOf(1, 3)
+        ),
+        HAS_NO_SUPPORTED_MODES(true,
+                listOf(),
+                arrayOf(createMode(1, 90f, 90f),
+                        createMode(2, 90f, 60f),
+                        createMode(3, 60f, 90f)),
+                listOf()
+        ),
+        HAS_NULL_SUPPORTED_MODES(true,
+                null,
+                arrayOf(createMode(1, 90f, 90f),
+                        createMode(2, 90f, 60f),
+                        createMode(3, 60f, 90f)),
+                listOf(1, 2, 3)
+        ),
+        HAS_SUPPORTED_MODES_VOTE_DISABLED(false,
                 listOf(),
                 arrayOf(createMode(1, 90f, 90f),
                         createMode(2, 90f, 60f),
@@ -81,10 +143,10 @@
 
     @Test
     fun `filters modes for summary supportedModes`(
-            @TestParameter testCase: SupportedModesVoteTestCase
+            @TestParameter testCase: SupportedModesTestCase
     ) {
-        val summary = createSummary(testCase.vsyncProximityVoteEnabled)
-        summary.supportedModes = testCase.summarySupportedModes
+        val summary = createSummary(testCase.supportedModesVoteEnabled)
+        summary.supportedModeIds = testCase.summarySupportedModes
 
         val result = summary.filterModes(testCase.modesToFilter)
 
@@ -96,8 +158,8 @@
             FloatArray(0), IntArray(0))
 }
 
-private fun createSummary(vsyncVoteEnabled: Boolean): VoteSummary {
-    val summary = createVotesSummary(vsyncProximityVoteEnabled = vsyncVoteEnabled)
+private fun createSummary(supportedModesVoteEnabled: Boolean): VoteSummary {
+    val summary = createVotesSummary(supportedModesVoteEnabled = supportedModesVoteEnabled)
     summary.width = 600
     summary.height = 800
     summary.maxPhysicalRefreshRate = Float.POSITIVE_INFINITY
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java
index 1f6f1a4..a248d6de 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -153,4 +154,51 @@
         assertThat(mVotesStorage.getVotes(DISPLAY_ID).size()).isEqualTo(0);
         verify(mVotesListener, never()).onChanged();
     }
+
+
+    @Test
+    public void removesAllVotesForPriority() {
+        // GIVEN vote storage with votes
+        mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE);
+        mVotesStorage.updateVote(DISPLAY_ID, PRIORITY_OTHER, VOTE_OTHER);
+        mVotesStorage.updateVote(DISPLAY_ID_OTHER, PRIORITY, VOTE);
+        mVotesStorage.updateVote(DISPLAY_ID_OTHER, PRIORITY_OTHER, VOTE_OTHER);
+        // WHEN removeAllVotesForPriority is called
+        mVotesStorage.removeAllVotesForPriority(PRIORITY);
+        // THEN votes with priority are removed from the storage
+        SparseArray<Vote> votes = mVotesStorage.getVotes(DISPLAY_ID);
+        assertThat(votes.size()).isEqualTo(1);
+        assertThat(votes.get(PRIORITY)).isNull();
+        votes = mVotesStorage.getVotes(DISPLAY_ID_OTHER);
+        assertThat(votes.size()).isEqualTo(1);
+        assertThat(votes.get(PRIORITY)).isNull();
+    }
+
+    @Test
+    public void removesAllVotesForPriority_notifiesListenerOnce() {
+        // GIVEN vote storage with votes
+        mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE);
+        mVotesStorage.updateVote(DISPLAY_ID, PRIORITY_OTHER, VOTE_OTHER);
+        mVotesStorage.updateVote(DISPLAY_ID_OTHER, PRIORITY, VOTE);
+        mVotesStorage.updateVote(DISPLAY_ID_OTHER, PRIORITY_OTHER, VOTE_OTHER);
+        clearInvocations(mVotesListener);
+        // WHEN removeAllVotesForPriority is called
+        mVotesStorage.removeAllVotesForPriority(PRIORITY);
+        // THEN listener notified once
+        verify(mVotesListener).onChanged();
+    }
+
+    @Test
+    public void removesAllVotesForPriority_noChangesIfNothingRemoved() {
+        // GIVEN vote storage with votes
+        mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE);
+        clearInvocations(mVotesListener);
+        // WHEN removeAllVotesForPriority is called for missing priority
+        mVotesStorage.removeAllVotesForPriority(PRIORITY_OTHER);
+        // THEN no changes to votes storage
+        SparseArray<Vote> votes = mVotesStorage.getVotes(DISPLAY_ID);
+        assertThat(votes.size()).isEqualTo(1);
+        assertThat(votes.get(PRIORITY)).isEqualTo(VOTE);
+        verify(mVotesListener, never()).onChanged();
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index bc7c9a5..6aa1825 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -582,7 +582,8 @@
 
     @Test
     public void testAutoLockPrivateProfile() {
-        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         UserManagerService mSpiedUms = spy(mUms);
         UserInfo privateProfileUser =
                 mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
@@ -600,7 +601,8 @@
 
     @Test
     public void testAutoLockOnDeviceLockForPrivateProfile() {
-        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
         UserManagerService mSpiedUms = spy(mUms);
         UserInfo privateProfileUser =
@@ -620,7 +622,8 @@
 
     @Test
     public void testAutoLockOnDeviceLockForPrivateProfile_keyguardUnlocked() {
-        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
         UserManagerService mSpiedUms = spy(mUms);
         UserInfo privateProfileUser =
@@ -638,7 +641,8 @@
 
     @Test
     public void testAutoLockOnDeviceLockForPrivateProfile_flagDisabled() {
-        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         mSetFlagsRule.disableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
         UserManagerService mSpiedUms = spy(mUms);
         UserInfo privateProfileUser =
@@ -657,7 +661,8 @@
 
     @Test
     public void testAutoLockAfterInactityForPrivateProfile() {
-        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
         UserManagerService mSpiedUms = spy(mUms);
         mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY);
@@ -679,7 +684,8 @@
 
     @Test
     public void testSetOrUpdateAutoLockPreference_noPrivateProfile() {
-        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
 
         mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
@@ -693,7 +699,8 @@
 
     @Test
     public void testSetOrUpdateAutoLockPreference() {
-        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
         mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
                         USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
@@ -743,6 +750,8 @@
 
     @Test
     public void testGetProfileIdsExcludingHidden() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_HIDING_PROFILES);
         UserInfo privateProfileUser =
                 mUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
@@ -754,7 +763,8 @@
 
     @Test
     public void testCreatePrivateProfileOnHeadlessSystemUser_shouldAllowCreation() {
-        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
         UserManagerService mSpiedUms = spy(mUms);
         int mainUser = mSpiedUms.getMainUserId();
@@ -766,7 +776,8 @@
 
     @Test
     public void testCreatePrivateProfileOnSecondaryUser_shouldNotAllowCreation() {
-        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
         UserInfo user = mUms.createUserWithThrow(generateLongString(), USER_TYPE_FULL_SECONDARY, 0);
         assertThat(mUms.canAddPrivateProfile(user.id)).isFalse();
@@ -777,7 +788,8 @@
 
     @Test
     public void testCreatePrivateProfileOnAutoDevices_shouldNotAllowCreation() {
-        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
         doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_AUTOMOTIVE), anyInt());
         int mainUser = mUms.getMainUserId();
@@ -789,7 +801,8 @@
 
     @Test
     public void testCreatePrivateProfileOnTV_shouldNotAllowCreation() {
-        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
         doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_LEANBACK), anyInt());
         int mainUser = mUms.getMainUserId();
@@ -801,7 +814,8 @@
 
     @Test
     public void testCreatePrivateProfileOnEmbedded_shouldNotAllowCreation() {
-        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
         doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_EMBEDDED), anyInt());
         int mainUser = mUms.getMainUserId();
@@ -813,7 +827,8 @@
 
     @Test
     public void testCreatePrivateProfileOnWatch_shouldNotAllowCreation() {
-        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
         doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_WATCH), anyInt());
         int mainUser = mUms.getMainUserId();
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/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
index aec896f..f86ff14 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -64,6 +64,7 @@
 import android.content.PermissionChecker;
 import android.content.res.Resources;
 import android.hardware.SensorManager;
+import android.hardware.devicestate.DeviceState;
 import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
 import android.hardware.display.AmbientDisplayConfiguration;
@@ -154,6 +155,8 @@
 
     private static final float BRIGHTNESS_FACTOR = 0.7f;
     private static final boolean BATTERY_SAVER_ENABLED = true;
+    private static final DeviceState DEVICE_STATE_1 = new DeviceState(
+            new DeviceState.Configuration.Builder(1 /* identifier */, "" /* name */).build());
 
     @Mock private BatterySaverController mBatterySaverControllerMock;
     @Mock private BatterySaverPolicy mBatterySaverPolicyMock;
@@ -2839,7 +2842,7 @@
 
         // Send a display state change event and advance the clock 10.
         final DeviceStateCallback deviceStateCallback = deviceStateCallbackCaptor.getValue();
-        deviceStateCallback.onStateChanged(1);
+        deviceStateCallback.onDeviceStateChanged(DEVICE_STATE_1);
         final long timeToAdvance = 10;
         advanceTime(timeToAdvance);
 
@@ -2849,7 +2852,7 @@
         assertThat(mService.wasDeviceIdleForInternal(timeToAdvance)).isFalse();
 
         // Send the same state and ensure that does not trigger an update.
-        deviceStateCallback.onStateChanged(1);
+        deviceStateCallback.onDeviceStateChanged(DEVICE_STATE_1);
         advanceTime(timeToAdvance);
         final long newTime = timeToAdvance * 2;
 
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 0089d4c..2724149 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -111,6 +111,8 @@
     <uses-permission android:name="android.permission.USE_BIOMETRIC_INTERNAL" />
     <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" />
     <uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.CAMERA" />
 
     <queries>
         <package android:name="com.android.servicestests.apps.suspendtestapp" />
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 7f88b00..eb89503 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -88,6 +88,7 @@
 
 import com.android.compatibility.common.util.TestUtils;
 import com.android.internal.R;
+import com.android.internal.accessibility.AccessibilityShortcutController;
 import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.accessibility.common.ShortcutConstants.FloatingMenuSize;
 import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
@@ -1318,6 +1319,152 @@
         }
     }
 
+    @Test
+    @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+    public void notifyQuickSettingsTilesChanged_statusBarServiceNotGranted_throwsException() {
+        mTestableContext.getTestablePermissions().setPermission(
+                Manifest.permission.STATUS_BAR_SERVICE, PackageManager.PERMISSION_DENIED);
+        mockManageAccessibilityGranted(mTestableContext);
+
+        assertThrows(SecurityException.class,
+                () -> mA11yms.notifyQuickSettingsTilesChanged(
+                        mA11yms.getCurrentUserState().mUserId,
+                        List.of(
+                                AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME)));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+    public void notifyQuickSettingsTilesChanged_manageAccessibilityNotGranted_throwsException() {
+        mockStatusBarServiceGranted(mTestableContext);
+        mTestableContext.getTestablePermissions().setPermission(
+                Manifest.permission.STATUS_BAR_SERVICE, PackageManager.PERMISSION_DENIED);
+
+        assertThrows(SecurityException.class,
+                () -> mA11yms.notifyQuickSettingsTilesChanged(
+                        mA11yms.getCurrentUserState().mUserId,
+                        List.of(
+                                AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME)));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+    public void notifyQuickSettingsTilesChanged_qsTileChanges_updateA11yTilesInQsPanel() {
+        mockStatusBarServiceGranted(mTestableContext);
+        mockManageAccessibilityGranted(mTestableContext);
+        List<ComponentName> tiles = List.of(
+                AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME,
+                AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME
+        );
+
+        mA11yms.notifyQuickSettingsTilesChanged(
+                mA11yms.getCurrentUserState().mUserId,
+                tiles
+        );
+
+        assertThat(
+                mA11yms.getCurrentUserState().getA11yQsTilesInQsPanel()
+        ).containsExactlyElementsIn(tiles);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+    public void notifyQuickSettingsTilesChanged_sameQsTiles_noUpdateToA11yTilesInQsPanel() {
+        notifyQuickSettingsTilesChanged_qsTileChanges_updateA11yTilesInQsPanel();
+        List<ComponentName> tiles =
+                mA11yms.getCurrentUserState().getA11yQsTilesInQsPanel().stream().toList();
+
+        mA11yms.notifyQuickSettingsTilesChanged(
+                mA11yms.getCurrentUserState().mUserId,
+                tiles
+        );
+
+        assertThat(
+                mA11yms.getCurrentUserState().getA11yQsTilesInQsPanel()
+        ).containsExactlyElementsIn(tiles);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+    public void notifyQuickSettingsTilesChanged_serviceWarningRequired_qsShortcutRemainDisabled() {
+        mockStatusBarServiceGranted(mTestableContext);
+        mockManageAccessibilityGranted(mTestableContext);
+        setupShortcutTargetServices();
+        ComponentName tile = new ComponentName(
+                TARGET_ALWAYS_ON_A11Y_SERVICE.getPackageName(),
+                TARGET_ALWAYS_ON_A11Y_SERVICE_TILE_CLASS);
+
+        mA11yms.notifyQuickSettingsTilesChanged(
+                mA11yms.getCurrentUserState().mUserId,
+                List.of(tile)
+        );
+
+        assertThat(mA11yms.getCurrentUserState().getA11yQsTargets()).doesNotContain(tile);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+    public void notifyQuickSettingsTilesChanged_serviceWarningNotRequired_qsShortcutEnabled() {
+        mockStatusBarServiceGranted(mTestableContext);
+        mockManageAccessibilityGranted(mTestableContext);
+        setupShortcutTargetServices();
+        final AccessibilityUserState userState = mA11yms.getCurrentUserState();
+        userState.mAccessibilityButtonTargets.clear();
+        userState.mAccessibilityButtonTargets.add(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString());
+        ComponentName tile = new ComponentName(
+                TARGET_ALWAYS_ON_A11Y_SERVICE.getPackageName(),
+                TARGET_ALWAYS_ON_A11Y_SERVICE_TILE_CLASS);
+
+        mA11yms.notifyQuickSettingsTilesChanged(
+                mA11yms.getCurrentUserState().mUserId,
+                List.of(tile)
+        );
+
+        assertThat(mA11yms.getCurrentUserState().getA11yQsTargets())
+                .contains(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+    public void notifyQuickSettingsTilesChanged_addFrameworkTile_qsShortcutEnabled() {
+        mockStatusBarServiceGranted(mTestableContext);
+        mockManageAccessibilityGranted(mTestableContext);
+        List<ComponentName> tiles = List.of(
+                AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME,
+                AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME
+        );
+
+        mA11yms.notifyQuickSettingsTilesChanged(
+                mA11yms.getCurrentUserState().mUserId,
+                tiles
+        );
+
+        assertThat(
+                mA11yms.getCurrentUserState().getA11yQsTargets()
+        ).containsExactlyElementsIn(List.of(
+                AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString(),
+                AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString())
+        );
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+    public void notifyQuickSettingsTilesChanged_removeFrameworkTile_qsShortcutDisabled() {
+        notifyQuickSettingsTilesChanged_addFrameworkTile_qsShortcutEnabled();
+        Set<ComponentName> qsTiles = mA11yms.getCurrentUserState().getA11yQsTilesInQsPanel();
+        qsTiles.remove(AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME);
+
+        mA11yms.notifyQuickSettingsTilesChanged(
+                mA11yms.getCurrentUserState().mUserId,
+                qsTiles.stream().toList()
+        );
+
+        assertThat(
+                mA11yms.getCurrentUserState().getA11yQsTargets()
+        ).doesNotContain(
+                AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString());
+    }
+
     private static AccessibilityServiceInfo mockAccessibilityServiceInfo(
             ComponentName componentName) {
         return mockAccessibilityServiceInfo(
@@ -1367,6 +1514,11 @@
                 PackageManager.PERMISSION_GRANTED);
     }
 
+    private void mockStatusBarServiceGranted(TestableContext context) {
+        context.getTestablePermissions().setPermission(Manifest.permission.STATUS_BAR_SERVICE,
+                PackageManager.PERMISSION_GRANTED);
+    }
+
     private void assertStartActivityWithExpectedComponentName(Context mockContext,
             String componentName) {
         verify(mockContext).startActivityAsUser(mIntentArgumentCaptor.capture(),
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
index f86cb7b..cda8b01 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
@@ -208,7 +208,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ADD_WINDOW_TOKEN_WITHOUT_LOCK)
     public void onServiceConnected_addsWindowTokens() {
         setServiceBinding(COMPONENT_NAME);
         mConnection.bindLocked();
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..b269beb9 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,9 +47,10 @@
 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;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.provider.Settings;
@@ -59,6 +62,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 +72,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Map;
+import java.util.Set;
+
 /** Tests for AccessibilityUserState */
 public class AccessibilityUserStateTest {
 
@@ -212,18 +219,6 @@
     }
 
     @Test
-    // addServiceLocked only calls addWindowTokensForAllDisplays when
-    // FLAG_ADD_WINDOW_TOKEN_WITHOUT_LOCK is off, so skip the test if it is on.
-    @RequiresFlagsDisabled(Flags.FLAG_ADD_WINDOW_TOKEN_WITHOUT_LOCK)
-    public void addService_flagDisabled_addsWindowTokens() {
-        when(mMockConnection.getComponentName()).thenReturn(COMPONENT_NAME);
-
-        mUserState.addServiceLocked(mMockConnection);
-
-        verify(mMockConnection).addWindowTokensForAllDisplays();
-    }
-
-    @Test
     public void reconcileSoftKeyboardMode_whenStateNotMatchSettings_setBothDefault() {
         // When soft kb show mode is hidden in settings and is auto in state.
         putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
@@ -431,7 +426,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/accessibility/UiAutomationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
index 3ee5f61..95a1f5a 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
@@ -39,7 +39,6 @@
 import android.content.pm.ServiceInfo;
 import android.hardware.display.DisplayManager;
 import android.os.IBinder;
-import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.view.WindowManager;
@@ -209,7 +208,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ADD_WINDOW_TOKEN_WITHOUT_LOCK)
     public void registerUiAutomationService_callsAddWindowTokenUsingHandler() {
         register(0);
         // registerUiTestAutomationServiceLocked() should not directly call addWindowToken.
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
index 2890078..8a6ba4d 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
@@ -67,7 +67,6 @@
         // Start some ops
         appOpsManager.startOp(AppOpsManager.OP_FINE_LOCATION);
         appOpsManager.startOp(AppOpsManager.OP_CAMERA);
-        appOpsManager.startOp(AppOpsManager.OP_RECORD_AUDIO);
 
         // Verify that we got called for the ops being started
         final InOrder inOrder = inOrder(listener);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 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/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 4e4bbfe..0290f0f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -21,6 +21,7 @@
 import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION;
 import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION_TO_USER;
 import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT;
 import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
 import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
 import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS;
@@ -63,6 +64,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.wm.LetterboxUiController.MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP;
 import static com.android.server.wm.LetterboxUiController.SET_ORIENTATION_REQUEST_COUNTER_TIMEOUT_MS;
+import static com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -81,6 +83,7 @@
 import android.content.res.Resources;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.view.InsetsSource;
 import android.view.InsetsState;
 import android.view.RoundedCorner;
@@ -101,11 +104,11 @@
 import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 
- /**
+/**
  * Test class for {@link LetterboxUiControllerTest}.
  *
  * Build/Install/Run:
- *  atest WmTests:LetterboxUiControllerTest
+ * atest WmTests:LetterboxUiControllerTest
  */
 @SmallTest
 @Presubmit
@@ -122,6 +125,8 @@
 
     @Rule
     public TestRule compatChangeRule = new PlatformCompatChangeRule();
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     private ActivityRecord mActivity;
     private Task mTask;
@@ -466,10 +471,48 @@
         assertTrue(mController.shouldForceRotateForCameraCompat());
     }
 
+    // shouldApplyFreeformTreatmentForCameraCompat
+
+    @Test
+    public void testShouldApplyCameraCompatFreeformTreatment_flagIsDisabled_returnsFalse() {
+        mSetFlagsRule.disableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM);
+
+        assertFalse(mController.shouldApplyFreeformTreatmentForCameraCompat());
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT})
+    public void testShouldApplyCameraCompatFreeformTreatment_overrideEnabled_returnsFalse() {
+        mSetFlagsRule.enableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM);
+
+        assertFalse(mController.shouldApplyFreeformTreatmentForCameraCompat());
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT})
+    public void testShouldApplyCameraCompatFreeformTreatment_disabledByOverride_returnsFalse()
+            throws Exception {
+        mSetFlagsRule.enableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM);
+
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        assertFalse(mController.shouldApplyFreeformTreatmentForCameraCompat());
+    }
+
+    @Test
+    public void testShouldApplyCameraCompatFreeformTreatment_notDisabledByOverride_returnsTrue()
+            throws Exception {
+        mSetFlagsRule.enableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM);
+
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        assertTrue(mController.shouldApplyFreeformTreatmentForCameraCompat());
+    }
+
     @Test
     public void testGetCropBoundsIfNeeded_handleCropForTransparentActivityBasedOnOpaqueBounds() {
         final InsetsSource taskbar = new InsetsSource(/*id=*/ 0,
-                 WindowInsets.Type.navigationBars());
+                WindowInsets.Type.navigationBars());
         taskbar.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER);
         final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
         final Rect opaqueBounds = new Rect(0, 0, 500, 300);
@@ -726,7 +769,7 @@
             throws Exception {
         mDisplayContent.setIgnoreOrientationRequest(false);
         assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
-             /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
+                /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
     }
 
     @Test
@@ -736,7 +779,7 @@
         prepareActivityThatShouldApplyUserMinAspectRatioOverride();
 
         assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
-             /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
+                /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
     }
 
     @Test
@@ -859,6 +902,7 @@
         assertEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded(
                 /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
     }
+
     @Test
     public void testOverrideOrientationIfNeeded_respectOrientationRequestOverUserFullScreen() {
         spyOn(mController);
@@ -1380,7 +1424,7 @@
 
     private void mockThatProperty(String propertyName, boolean value) throws Exception {
         Property property = new Property(propertyName, /* value */ value, /* packageName */ "",
-                 /* className */ "");
+                /* className */ "");
         PackageManager pm = mWm.mContext.getPackageManager();
         spyOn(pm);
         doReturn(property).when(pm).getProperty(eq(propertyName), anyString());
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..3bd6496 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 */);
@@ -606,6 +623,23 @@
         doReturn(true).when(root).fillsParent();
     }
 
+    @Test
+    public void testIsTopActivityTranslucent() {
+        DisplayContent display = mAtm.mRootWindowContainer.getDefaultDisplay();
+        final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build();
+        final Task task = rootTask.getBottomMostTask();
+        final ActivityRecord root = task.getTopNonFinishingActivity();
+        spyOn(mWm.mLetterboxConfiguration);
+        spyOn(root);
+
+        doReturn(false).when(root).fillsParent();
+        assertTrue(task.getTaskInfo().isTopActivityTransparent);
+
+        doReturn(true).when(root).fillsParent();
+        assertFalse(task.getTaskInfo().isTopActivityTransparent);
+    }
+
     /**
      * Tests that a task with forced orientation has orientation-consistent bounds within the
      * parent.
@@ -1823,6 +1857,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/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 03b695d..43b424f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -1376,8 +1376,9 @@
         assertTrue(w1.syncNextBuffer());
         assertTrue(w2.syncNextBuffer());
 
-        // A drawn window can complete the sync state automatically.
+        // A drawn window in non-explicit sync can complete the sync state automatically.
         w1.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN;
+        w1.mPrepareSyncSeqId = 0;
         makeLastConfigReportedToClient(w1, true /* visible */);
         mWm.mSyncEngine.onSurfacePlacement();
         verify(mockCallback).onTransactionReady(anyInt(), any());
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 77b2638..9acda5f 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -142,8 +142,6 @@
             "/sys/class/android_usb/android0/state";
     private static final String RNDIS_ETH_ADDR_PATH =
             "/sys/class/android_usb/android0/f_rndis/ethaddr";
-    private static final String AUDIO_SOURCE_PCM_PATH =
-            "/sys/class/android_usb/android0/f_audio_source/pcm";
     private static final String MIDI_ALSA_PATH =
             "/sys/class/android_usb/android0/f_midi/alsa";
 
@@ -172,8 +170,6 @@
     private static final int MSG_UPDATE_USB_SPEED = 22;
     private static final int MSG_UPDATE_HAL_VERSION = 23;
 
-    private static final int AUDIO_MODE_SOURCE = 1;
-
     // Delay for debouncing USB disconnects.
     // We often get rapid connect/disconnect events when enabling USB functions,
     // which need debouncing.
@@ -464,7 +460,6 @@
         int operationId = sUsbOperationCount.incrementAndGet();
 
         mAccessoryStrings = nativeGetAccessoryStrings();
-        boolean enableAudio = (nativeGetAudioMode() == AUDIO_MODE_SOURCE);
         // don't start accessory mode if our mandatory strings have not been set
         boolean enableAccessory = (mAccessoryStrings != null &&
                 mAccessoryStrings[UsbAccessory.MANUFACTURER_STRING] != null &&
@@ -474,9 +469,6 @@
         if (enableAccessory) {
             functions |= UsbManager.FUNCTION_ACCESSORY;
         }
-        if (enableAudio) {
-            functions |= UsbManager.FUNCTION_AUDIO_SOURCE;
-        }
 
         if (functions != UsbManager.FUNCTION_NONE) {
             mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_ACCESSORY_MODE_ENTER_TIMEOUT),
@@ -2490,6 +2482,4 @@
     private native FileDescriptor nativeOpenControl(String usbFunction);
 
     private native boolean nativeIsStartRequested();
-
-    private native int nativeGetAudioMode();
 }
diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java
index 5ba5ee883..8b9a93b 100644
--- a/telecomm/java/android/telecom/DisconnectCause.java
+++ b/telecomm/java/android/telecom/DisconnectCause.java
@@ -16,6 +16,8 @@
 
 package android.telecom;
 
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -28,8 +30,11 @@
 import android.telephony.ims.ImsReasonInfo;
 import android.text.TextUtils;
 
+import androidx.annotation.IntDef;
+
 import com.android.server.telecom.flags.Flags;
 
+import java.lang.annotation.Retention;
 import java.util.Objects;
 
 /**
@@ -42,48 +47,69 @@
 public final class DisconnectCause implements Parcelable {
 
     /** Disconnected because of an unknown or unspecified reason. */
-    public static final int UNKNOWN = TelecomProtoEnums.UNKNOWN; // = 0
+    public static final int UNKNOWN = 0;
     /** Disconnected because there was an error, such as a problem with the network. */
-    public static final int ERROR = TelecomProtoEnums.ERROR; // = 1
+    public static final int ERROR = 1;
     /** Disconnected because of a local user-initiated action, such as hanging up. */
-    public static final int LOCAL = TelecomProtoEnums.LOCAL; // = 2
+    public static final int LOCAL = 2;
     /**
      * Disconnected because the remote party hung up an ongoing call, or because an outgoing call
      * was not answered by the remote party.
      */
-    public static final int REMOTE = TelecomProtoEnums.REMOTE; // = 3
+    public static final int REMOTE = 3;
     /** Disconnected because it has been canceled. */
-    public static final int CANCELED = TelecomProtoEnums.CANCELED; // = 4
+    public static final int CANCELED = 4;
     /** Disconnected because there was no response to an incoming call. */
-    public static final int MISSED = TelecomProtoEnums.MISSED; // = 5
+    public static final int MISSED = 5;
     /** Disconnected because the user rejected an incoming call. */
-    public static final int REJECTED = TelecomProtoEnums.REJECTED; // = 6
+    public static final int REJECTED = 6;
     /** Disconnected because the other party was busy. */
-    public static final int BUSY = TelecomProtoEnums.BUSY; // = 7
+    public static final int BUSY = 7;
     /**
      * Disconnected because of a restriction on placing the call, such as dialing in airplane
      * mode.
      */
-    public static final int RESTRICTED = TelecomProtoEnums.RESTRICTED; // = 8
+    public static final int RESTRICTED = 8;
     /** Disconnected for reason not described by other disconnect codes. */
-    public static final int OTHER = TelecomProtoEnums.OTHER; // = 9
+    public static final int OTHER = 9;
     /**
      * Disconnected because the connection manager did not support the call. The call will be tried
      * again without a connection manager. See {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}.
      */
-    public static final int CONNECTION_MANAGER_NOT_SUPPORTED =
-            TelecomProtoEnums.CONNECTION_MANAGER_NOT_SUPPORTED; // = 10
+    public static final int CONNECTION_MANAGER_NOT_SUPPORTED = 10;
 
     /**
      * Disconnected because the user did not locally answer the incoming call, but it was answered
      * on another device where the call was ringing.
      */
-    public static final int ANSWERED_ELSEWHERE = TelecomProtoEnums.ANSWERED_ELSEWHERE; // = 11
+    public static final int ANSWERED_ELSEWHERE = 11;
 
     /**
      * Disconnected because the call was pulled from the current device to another device.
      */
-    public static final int CALL_PULLED = TelecomProtoEnums.CALL_PULLED; // = 12
+    public static final int CALL_PULLED = 12;
+
+    /**
+     * @hide
+     */
+    @Retention(SOURCE)
+    @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+    @IntDef({
+            UNKNOWN,
+            ERROR,
+            LOCAL,
+            REMOTE,
+            CANCELED,
+            MISSED,
+            REJECTED,
+            BUSY,
+            RESTRICTED,
+            OTHER,
+            CONNECTION_MANAGER_NOT_SUPPORTED,
+            ANSWERED_ELSEWHERE,
+            CALL_PULLED
+    })
+    public @interface DisconnectCauseCode {}
 
     /**
      * Reason code (returned via {@link #getReason()}) which indicates that a call could not be
@@ -116,7 +142,7 @@
      */
     public static final String REASON_EMERGENCY_CALL_PLACED = "REASON_EMERGENCY_CALL_PLACED";
 
-    private int mDisconnectCode;
+    private @DisconnectCauseCode int mDisconnectCode;
     private CharSequence mDisconnectLabel;
     private CharSequence mDisconnectDescription;
     private String mDisconnectReason;
@@ -130,7 +156,7 @@
      *
      * @param code The code for the disconnect cause.
      */
-    public DisconnectCause(int code) {
+    public DisconnectCause(@DisconnectCauseCode int code) {
         this(code, null, null, null, ToneGenerator.TONE_UNKNOWN);
     }
 
@@ -140,7 +166,7 @@
      * @param code The code for the disconnect cause.
      * @param reason The reason for the disconnect.
      */
-    public DisconnectCause(int code, String reason) {
+    public DisconnectCause(@DisconnectCauseCode int code, String reason) {
         this(code, null, null, reason, ToneGenerator.TONE_UNKNOWN);
     }
 
@@ -152,7 +178,8 @@
      * @param description The localized description to show to the user to explain the disconnect.
      * @param reason The reason for the disconnect.
      */
-    public DisconnectCause(int code, CharSequence label, CharSequence description, String reason) {
+    public DisconnectCause(@DisconnectCauseCode int code, CharSequence label,
+            CharSequence description, String reason) {
         this(code, label, description, reason, ToneGenerator.TONE_UNKNOWN);
     }
 
@@ -165,8 +192,8 @@
      * @param reason The reason for the disconnect.
      * @param toneToPlay The tone to play on disconnect, as defined in {@link ToneGenerator}.
      */
-    public DisconnectCause(int code, CharSequence label, CharSequence description, String reason,
-            int toneToPlay) {
+    public DisconnectCause(@DisconnectCauseCode int code, CharSequence label,
+            CharSequence description, String reason, int toneToPlay) {
         this(code, label, description, reason, toneToPlay,
                 android.telephony.DisconnectCause.ERROR_UNSPECIFIED,
                 PreciseDisconnectCause.ERROR_UNSPECIFIED, null /* imsReasonInfo */);
@@ -186,8 +213,8 @@
      * @param imsReasonInfo The relevant {@link ImsReasonInfo}, or {@code null} if not available.
      * @hide
      */
-    public DisconnectCause(int code, @NonNull CharSequence label,
-            @NonNull CharSequence description, @NonNull String reason,
+    public DisconnectCause(@DisconnectCauseCode int code, @Nullable CharSequence label,
+            @Nullable CharSequence description, @Nullable String reason,
             int toneToPlay, @Annotation.DisconnectCauses int telephonyDisconnectCause,
             @Annotation.PreciseDisconnectCauses int telephonyPreciseDisconnectCause,
             @Nullable ImsReasonInfo imsReasonInfo) {
@@ -206,7 +233,7 @@
      *
      * @return The disconnect code.
      */
-    public int getCode() {
+    public @DisconnectCauseCode int getCode() {
         return mDisconnectCode;
     }
 
@@ -301,28 +328,24 @@
     @SystemApi
     @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
     public static final class Builder {
-        private int mDisconnectCode;
+        private @DisconnectCauseCode int mDisconnectCode;
         private CharSequence mDisconnectLabel;
         private CharSequence mDisconnectDescription;
         private String mDisconnectReason;
-        private int mToneToPlay;
+        private int mToneToPlay = ToneGenerator.TONE_UNKNOWN;
         private int mTelephonyDisconnectCause;
         private int mTelephonyPreciseDisconnectCause;
         private ImsReasonInfo mImsReasonInfo;
 
-        /**
-         * Sets the code for the reason for this disconnect.
-         * @param code The code denoting the type of disconnect.
-         */
-        public @NonNull DisconnectCause.Builder setCode(int code) {
+        public Builder(@DisconnectCauseCode int code) {
             mDisconnectCode = code;
-            return this;
         }
 
         /**
          * Sets a label which explains the reason for the disconnect cause, used for display in the
          * user interface.
          * @param label The label to associate with the disconnect cause.
+         * @return The {@link DisconnectCause} builder instance.
          */
         public @NonNull DisconnectCause.Builder setLabel(@Nullable CharSequence label) {
             mDisconnectLabel = label;
@@ -333,6 +356,7 @@
          * Sets a description which provides the reason for the disconnect cause, used for display
          * in the user interface.
          * @param description The description to associate with the disconnect cause.
+         * @return The {@link DisconnectCause} builder instance.
          */
         public @NonNull DisconnectCause.Builder setDescription(
                 @Nullable CharSequence description) {
@@ -344,6 +368,7 @@
          * Sets a reason providing explanation for the disconnect (intended for logging and not for
          * displaying in the user interface).
          * @param reason The reason for the disconnect.
+         * @return The {@link DisconnectCause} builder instance.
          */
         public @NonNull DisconnectCause.Builder setReason(@NonNull String reason) {
             mDisconnectReason = reason;
@@ -353,6 +378,7 @@
         /**
          * Sets the tone to play when disconnected.
          * @param toneToPlay The tone as defined in {@link ToneGenerator} to play when disconnected.
+         * @return The {@link DisconnectCause} builder instance.
          */
         public @NonNull DisconnectCause.Builder setTone(int toneToPlay) {
             mToneToPlay = toneToPlay;
@@ -363,6 +389,7 @@
          * Sets the telephony {@link android.telephony.DisconnectCause} for the call (used
          * internally by Telecom for providing extra debug information from Telephony).
          * @param telephonyDisconnectCause The disconnect cause as provided by Telephony.
+         * @return The {@link DisconnectCause} builder instance.
          */
         public @NonNull DisconnectCause.Builder setTelephonyDisconnectCause(
                 @Annotation.DisconnectCauses int telephonyDisconnectCause) {
@@ -375,6 +402,7 @@
          * internally by Telecom for providing extra debug information from Telephony).
          * @param telephonyPreciseDisconnectCause The precise disconnect cause as provided by
          *                                        Telephony.
+         * @return The {@link DisconnectCause} builder instance.
          */
 
         public @NonNull DisconnectCause.Builder setTelephonyPreciseDisconnectCause(
@@ -384,10 +412,11 @@
         }
 
         /**
-         * Returns the telephony {@link ImsReasonInfo} associated with the call disconnection. This
+         * Sets the telephony {@link ImsReasonInfo} associated with the call disconnection. This
          * is only used internally by Telecom for providing extra debug information from Telephony.
          *
          * @param imsReasonInfo The {@link ImsReasonInfo} or {@code null} if not known.
+         * @return The {@link DisconnectCause} builder instance.
          */
         public @NonNull DisconnectCause.Builder setImsReasonInfo(
                 @Nullable ImsReasonInfo imsReasonInfo) {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index d3a50bb..df32fbd 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -3315,6 +3315,18 @@
             "support_no_reply_timer_for_cfnry_bool";
 
     /**
+     * No reply time value to be sent to network for call forwarding on no reply
+     * (CFNRy 3GPP TS 24.082 version 17.0 section 3).
+     * Controls time in seconds for the no reply condition on in the call forwarding
+     * settings UI.
+     * This is available when {@link #KEY_SUPPORT_NO_REPLY_TIMER_FOR_CFNRY_BOOL} is true.
+     *
+     * @hide
+     */
+    public static final String KEY_NO_REPLY_TIMER_FOR_CFNRY_SEC_INT =
+            "no_reply_timer_for_cfnry_sec_int";
+
+    /**
      * List of the FAC (feature access codes) to dial as a normal call.
      * @hide
      */
@@ -10666,6 +10678,7 @@
         sDefaults.putBoolean(KEY_VT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_RTT_CALL_BOOL, true);
         sDefaults.putBoolean(KEY_DISABLE_CHARGE_INDICATION_BOOL, false);
         sDefaults.putBoolean(KEY_SUPPORT_NO_REPLY_TIMER_FOR_CFNRY_BOOL, true);
+        sDefaults.putInt(KEY_NO_REPLY_TIMER_FOR_CFNRY_SEC_INT, 20);
         sDefaults.putStringArray(KEY_FEATURE_ACCESS_CODES_STRING_ARRAY, null);
         sDefaults.putBoolean(KEY_IDENTIFY_HIGH_DEFINITION_CALLS_IN_CALL_LOG_BOOL, false);
         sDefaults.putBoolean(KEY_SHOW_PRECISE_FAILED_CAUSE_BOOL, false);
diff --git a/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png b/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png
index 443de8e..7cdab94 100644
--- a/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png
+++ b/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png
index c51da05..377288d 100644
--- a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png
+++ b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png b/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png
index ab23401..68b1473 100644
--- a/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png
+++ b/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png
Binary files differ
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
             )