Merge "Add ReportRenderingThreads function to WebView function table" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 8591a9c..6ecd38f 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -21,6 +21,7 @@
         // !!! KEEP THIS LIST ALPHABETICAL !!!
         "aconfig_mediacodec_flags_java_lib",
         "android.adaptiveauth.flags-aconfig-java",
+        "android.app.contextualsearch.flags-aconfig-java",
         "android.app.flags-aconfig-java",
         "android.app.ondeviceintelligence-aconfig-java",
         "android.app.smartspace.flags-aconfig-java",
@@ -974,6 +975,19 @@
     ],
 }
 
+// Contextual Search
+aconfig_declarations {
+    name: "android.app.contextualsearch.flags-aconfig",
+    package: "android.app.contextualsearch.flags",
+    srcs: ["core/java/android/app/contextualsearch/flags.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.app.contextualsearch.flags-aconfig-java",
+    aconfig_declarations: "android.app.contextualsearch.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Smartspace
 aconfig_declarations {
     name: "android.app.smartspace.flags-aconfig",
diff --git a/core/api/current.txt b/core/api/current.txt
index 173cf6c..925cf4c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -293,7 +293,7 @@
     field public static final String SET_ALARM = "com.android.alarm.permission.SET_ALARM";
     field public static final String SET_ALWAYS_FINISH = "android.permission.SET_ALWAYS_FINISH";
     field public static final String SET_ANIMATION_SCALE = "android.permission.SET_ANIMATION_SCALE";
-    field @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public static final String SET_BIOMETRIC_DIALOG_LOGO = "android.permission.SET_BIOMETRIC_DIALOG_LOGO";
+    field @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public static final String SET_BIOMETRIC_DIALOG_ADVANCED = "android.permission.SET_BIOMETRIC_DIALOG_ADVANCED";
     field public static final String SET_DEBUG_APP = "android.permission.SET_DEBUG_APP";
     field @Deprecated public static final String SET_PREFERRED_APPLICATIONS = "android.permission.SET_PREFERRED_APPLICATIONS";
     field public static final String SET_PROCESS_LIMIT = "android.permission.SET_PROCESS_LIMIT";
@@ -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);
@@ -18904,9 +18904,9 @@
     method @Nullable public int getAllowedAuthenticators();
     method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable public android.hardware.biometrics.PromptContentView getContentView();
     method @Nullable public CharSequence getDescription();
-    method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public android.graphics.Bitmap getLogoBitmap();
-    method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public String getLogoDescription();
-    method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @DrawableRes @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public int getLogoRes();
+    method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED) public android.graphics.Bitmap getLogoBitmap();
+    method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED) public String getLogoDescription();
+    method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @DrawableRes @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED) public int getLogoRes();
     method @Nullable public CharSequence getNegativeButtonText();
     method @Nullable public CharSequence getSubtitle();
     method @NonNull public CharSequence getTitle();
@@ -18956,9 +18956,9 @@
     method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setContentView(@NonNull android.hardware.biometrics.PromptContentView);
     method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDescription(@NonNull CharSequence);
     method @Deprecated @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDeviceCredentialAllowed(boolean);
-    method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public android.hardware.biometrics.BiometricPrompt.Builder setLogoBitmap(@NonNull android.graphics.Bitmap);
-    method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public android.hardware.biometrics.BiometricPrompt.Builder setLogoDescription(@NonNull String);
-    method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public android.hardware.biometrics.BiometricPrompt.Builder setLogoRes(@DrawableRes int);
+    method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED) public android.hardware.biometrics.BiometricPrompt.Builder setLogoBitmap(@NonNull android.graphics.Bitmap);
+    method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED) public android.hardware.biometrics.BiometricPrompt.Builder setLogoDescription(@NonNull String);
+    method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED) public android.hardware.biometrics.BiometricPrompt.Builder setLogoRes(@DrawableRes int);
     method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setNegativeButton(@NonNull CharSequence, @NonNull java.util.concurrent.Executor, @NonNull android.content.DialogInterface.OnClickListener);
     method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setSubtitle(@NonNull CharSequence);
     method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setTitle(@NonNull CharSequence);
@@ -18971,6 +18971,7 @@
     ctor @Deprecated public BiometricPrompt.CryptoObject(@NonNull android.security.identity.IdentityCredential);
     ctor public BiometricPrompt.CryptoObject(@NonNull android.security.identity.PresentationSession);
     ctor @FlaggedApi("android.hardware.biometrics.add_key_agreement_crypto_object") public BiometricPrompt.CryptoObject(@NonNull javax.crypto.KeyAgreement);
+    ctor @FlaggedApi("android.hardware.biometrics.get_op_id_crypto_object") public BiometricPrompt.CryptoObject(long);
     method @Nullable public javax.crypto.Cipher getCipher();
     method @Deprecated @Nullable public android.security.identity.IdentityCredential getIdentityCredential();
     method @FlaggedApi("android.hardware.biometrics.add_key_agreement_crypto_object") @Nullable public javax.crypto.KeyAgreement getKeyAgreement();
@@ -19000,6 +19001,21 @@
   @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public interface PromptContentView {
   }
 
+  @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptContentViewWithMoreOptionsButton implements android.os.Parcelable android.hardware.biometrics.PromptContentView {
+    method public int describeContents();
+    method @Nullable @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED) public String getDescription();
+    method @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED) public android.content.DialogInterface.OnClickListener getMoreOptionsButtonListener();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.biometrics.PromptContentViewWithMoreOptionsButton> CREATOR;
+  }
+
+  public static final class PromptContentViewWithMoreOptionsButton.Builder {
+    ctor public PromptContentViewWithMoreOptionsButton.Builder();
+    method @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED) public android.hardware.biometrics.PromptContentViewWithMoreOptionsButton build();
+    method @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED) public android.hardware.biometrics.PromptContentViewWithMoreOptionsButton.Builder setDescription(@NonNull String);
+    method @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED) public android.hardware.biometrics.PromptContentViewWithMoreOptionsButton.Builder setMoreOptionsButtonListener(@NonNull java.util.concurrent.Executor, @NonNull android.content.DialogInterface.OnClickListener);
+  }
+
   @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptVerticalListContentView implements android.os.Parcelable android.hardware.biometrics.PromptContentView {
     method public int describeContents();
     method @Nullable public String getDescription();
@@ -20121,9 +20137,14 @@
     ctor public OutputConfiguration(@NonNull android.view.Surface);
     ctor public OutputConfiguration(int, @NonNull android.view.Surface);
     ctor public <T> OutputConfiguration(@NonNull android.util.Size, @NonNull Class<T>);
+    ctor @FlaggedApi("com.android.internal.camera.flags.camera_device_setup") public OutputConfiguration(int, @NonNull android.util.Size);
+    ctor @FlaggedApi("com.android.internal.camera.flags.camera_device_setup") public OutputConfiguration(int, int, @NonNull android.util.Size);
+    ctor @FlaggedApi("com.android.internal.camera.flags.camera_device_setup") public OutputConfiguration(int, @NonNull android.util.Size, long);
+    ctor @FlaggedApi("com.android.internal.camera.flags.camera_device_setup") public OutputConfiguration(int, int, @NonNull android.util.Size, long);
     method public void addSensorPixelModeUsed(int);
     method public void addSurface(@NonNull android.view.Surface);
     method @NonNull public static java.util.Collection<android.hardware.camera2.params.OutputConfiguration> createInstancesForMultiResolutionOutput(@NonNull android.hardware.camera2.MultiResolutionImageReader);
+    method @FlaggedApi("com.android.internal.camera.flags.camera_device_setup") @NonNull public static java.util.List<android.hardware.camera2.params.OutputConfiguration> createInstancesForMultiResolutionOutput(@NonNull java.util.Collection<android.hardware.camera2.params.MultiResolutionStreamInfo>, int);
     method public int describeContents();
     method public void enableSurfaceSharing();
     method public long getDynamicRangeProfile();
@@ -20142,6 +20163,7 @@
     method public void setPhysicalCameraId(@Nullable String);
     method public void setReadoutTimestampEnabled(boolean);
     method public void setStreamUseCase(long);
+    method @FlaggedApi("com.android.internal.camera.flags.camera_device_setup") public static void setSurfacesForMultiResolutionOutput(@NonNull java.util.Collection<android.hardware.camera2.params.OutputConfiguration>, @NonNull android.hardware.camera2.MultiResolutionImageReader);
     method public void setTimestampBase(int);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.camera2.params.OutputConfiguration> CREATOR;
@@ -20203,6 +20225,7 @@
 
   public final class SessionConfiguration implements android.os.Parcelable {
     ctor public SessionConfiguration(int, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback);
+    ctor @FlaggedApi("com.android.internal.camera.flags.camera_device_setup") public SessionConfiguration(int, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>);
     method public void clearColorSpace();
     method public int describeContents();
     method @Nullable public android.graphics.ColorSpace getColorSpace();
@@ -20212,6 +20235,7 @@
     method public android.hardware.camera2.CaptureRequest getSessionParameters();
     method public int getSessionType();
     method public android.hardware.camera2.CameraCaptureSession.StateCallback getStateCallback();
+    method @FlaggedApi("com.android.internal.camera.flags.camera_device_setup") public void setCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback);
     method public void setColorSpace(@NonNull android.graphics.ColorSpace.Named);
     method public void setInputConfiguration(@NonNull android.hardware.camera2.params.InputConfiguration);
     method public void setSessionParameters(android.hardware.camera2.CaptureRequest);
@@ -33960,6 +33984,7 @@
     method public static boolean supportsMultipleUsers();
     field public static final String ALLOW_PARENT_PROFILE_APP_LINKING = "allow_parent_profile_app_linking";
     field @Deprecated public static final String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile";
+    field @FlaggedApi("android.os.allow_private_profile") public static final String DISALLOW_ADD_PRIVATE_PROFILE = "no_add_private_profile";
     field public static final String DISALLOW_ADD_USER = "no_add_user";
     field public static final String DISALLOW_ADD_WIFI_CONFIG = "no_add_wifi_config";
     field public static final String DISALLOW_ADJUST_VOLUME = "no_adjust_volume";
@@ -52193,7 +52218,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 +52243,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 +52258,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 +52274,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 +52333,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);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index a551f0d..9039bf1 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -7,6 +7,7 @@
     field public static final String ACCESS_BROADCAST_RADIO = "android.permission.ACCESS_BROADCAST_RADIO";
     field public static final String ACCESS_BROADCAST_RESPONSE_STATS = "android.permission.ACCESS_BROADCAST_RESPONSE_STATS";
     field public static final String ACCESS_CACHE_FILESYSTEM = "android.permission.ACCESS_CACHE_FILESYSTEM";
+    field @FlaggedApi("android.app.contextualsearch.flags.enable_service") public static final String ACCESS_CONTEXTUAL_SEARCH = "android.permission.ACCESS_CONTEXTUAL_SEARCH";
     field public static final String ACCESS_CONTEXT_HUB = "android.permission.ACCESS_CONTEXT_HUB";
     field public static final String ACCESS_DRM_CERTIFICATES = "android.permission.ACCESS_DRM_CERTIFICATES";
     field @Deprecated public static final String ACCESS_FM_RADIO = "android.permission.ACCESS_FM_RADIO";
@@ -198,7 +199,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 +282,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 +1329,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);
@@ -2176,6 +2177,39 @@
 
 }
 
+package android.app.contextualsearch {
+
+  @FlaggedApi("android.app.contextualsearch.flags.enable_service") public class ContextualSearchManager {
+    method public void getContextualSearchState(@NonNull android.os.IBinder, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.contextualsearch.ContextualSearchState,java.lang.Throwable>);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXTUAL_SEARCH) public void startContextualSearch(int);
+    field public static final String ACTION_LAUNCH_CONTEXTUAL_SEARCH = "android.app.contextualsearch.action.LAUNCH_CONTEXTUAL_SEARCH";
+    field public static final int ENTRYPOINT_LONG_PRESS_HOME = 2; // 0x2
+    field public static final int ENTRYPOINT_LONG_PRESS_META = 10; // 0xa
+    field public static final int ENTRYPOINT_LONG_PRESS_NAV_HANDLE = 1; // 0x1
+    field public static final int ENTRYPOINT_LONG_PRESS_OVERVIEW = 3; // 0x3
+    field public static final int ENTRYPOINT_OVERVIEW_ACTION = 4; // 0x4
+    field public static final int ENTRYPOINT_OVERVIEW_MENU = 5; // 0x5
+    field public static final int ENTRYPOINT_SYSTEM_ACTION = 9; // 0x9
+    field public static final String EXTRA_ENTRYPOINT = "android.app.contextualsearch.extra.ENTRYPOINT";
+    field public static final String EXTRA_FLAG_SECURE_FOUND = "android.app.contextualsearch.extra.FLAG_SECURE_FOUND";
+    field public static final String EXTRA_IS_MANAGED_PROFILE_VISIBLE = "android.app.contextualsearch.extra.IS_MANAGED_PROFILE_VISIBLE";
+    field public static final String EXTRA_SCREENSHOT = "android.app.contextualsearch.extra.SCREENSHOT";
+    field public static final String EXTRA_TOKEN = "android.app.contextualsearch.extra.TOKEN";
+    field public static final String EXTRA_VISIBLE_PACKAGE_NAMES = "android.app.contextualsearch.extra.VISIBLE_PACKAGE_NAMES";
+  }
+
+  @FlaggedApi("android.app.contextualsearch.flags.enable_service") public final class ContextualSearchState implements android.os.Parcelable {
+    ctor public ContextualSearchState(@Nullable android.app.assist.AssistStructure, @Nullable android.app.assist.AssistContent, @NonNull android.os.Bundle);
+    method public int describeContents();
+    method @Nullable public android.app.assist.AssistContent getContent();
+    method @NonNull public android.os.Bundle getExtras();
+    method @Nullable public android.app.assist.AssistStructure getStructure();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.contextualsearch.ContextualSearchState> CREATOR;
+  }
+
+}
+
 package android.app.job {
 
   public abstract class JobScheduler {
@@ -3327,7 +3361,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>);
@@ -3747,6 +3781,7 @@
     field public static final String CLOUDSEARCH_SERVICE = "cloudsearch";
     field public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions";
     field public static final String CONTEXTHUB_SERVICE = "contexthub";
+    field @FlaggedApi("android.app.contextualsearch.flags.enable_service") public static final String CONTEXTUAL_SEARCH_SERVICE = "contextual_search";
     field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String ECM_ENHANCED_CONFIRMATION_SERVICE = "ecm_enhanced_confirmation";
     field public static final String ETHERNET_SERVICE = "ethernet";
     field public static final String EUICC_CARD_SERVICE = "euicc_card";
@@ -5006,7 +5041,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>);
   }
 
@@ -6192,8 +6227,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;
   }
@@ -12981,11 +13014,11 @@
     method public long getMaximumDataBlockSize();
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_OEM_UNLOCK_STATE, "android.permission.OEM_UNLOCK_STATE"}) public boolean getOemUnlockEnabled();
     method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_PDB_STATE) public String getPersistentDataPackageName();
-    method public byte[] read();
+    method @Nullable public byte[] read();
     method @FlaggedApi("android.security.frp_enforcement") public boolean setFactoryResetProtectionSecret(@NonNull byte[]);
     method @Deprecated @RequiresPermission("android.permission.OEM_UNLOCK_STATE") public void setOemUnlockEnabled(boolean);
     method @RequiresPermission("android.permission.OEM_UNLOCK_STATE") public void wipe();
-    method public int write(byte[]);
+    method public int write(@Nullable byte[]);
     field public static final int FLASH_LOCK_LOCKED = 1; // 0x1
     field public static final int FLASH_LOCK_UNKNOWN = -1; // 0xffffffff
     field public static final int FLASH_LOCK_UNLOCKED = 0; // 0x0
@@ -13938,9 +13971,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/test-current.txt b/core/api/test-current.txt
index bc45a76..0a26490 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1625,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();
@@ -3053,6 +3065,7 @@
   }
 
   public static final class DreamService.DreamMetadata {
+    field @FlaggedApi("android.service.controls.flags.home_panel_dream") @NonNull public final int dreamCategory;
     field @Nullable public final android.graphics.drawable.Drawable previewImage;
     field @Nullable public final android.content.ComponentName settingsActivity;
     field @NonNull public final boolean showComplications;
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..6f6e091 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -274,11 +274,9 @@
     private Context mOuterContext;
 
     private final Object mThemeLock = new Object();
-
     @UnsupportedAppUsage
     @GuardedBy("mThemeLock")
     private int mThemeResource = 0;
-
     @UnsupportedAppUsage
     @GuardedBy("mThemeLock")
     private Resources.Theme mTheme = null;
@@ -347,17 +345,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("mDirsLock")
+    private File mDatabasesDir;
+    @GuardedBy("mDirsLock")
+    @UnsupportedAppUsage
+    private File mPreferencesDir;
+    @GuardedBy("mDirsLock")
+    private File mFilesDir;
+    @GuardedBy("mDirsLock")
+    private File mCratesDir;
+    @GuardedBy("mDirsLock")
+    private File mNoBackupFilesDir;
+    @GuardedBy("mDirsLock")
+    private File mCacheDir;
+    @GuardedBy("mDirsLock")
+    private File mCodeCacheDir;
 
     // The system service cache for the system services that are cached per-ContextImpl.
     @UnsupportedAppUsage
@@ -491,7 +493,6 @@
         }
     }
 
-    @GuardedBy("mThemeLock")
     private void initializeTheme() {
         if (mTheme == null) {
             mTheme = mResources.newTheme();
@@ -741,18 +742,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 (mDirsLock) {
+            if (mPreferencesDir == null) {
+                mPreferencesDir = new File(getDataDir(), "shared_prefs");
             }
+            return ensurePrivateDirExists(mPreferencesDir);
         }
-        return localPreferencesDir;
     }
 
     @Override
@@ -794,16 +789,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 +826,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 (mDirsLock) {
+            if (mFilesDir == null) {
+                mFilesDir = new File(getDataDir(), "files");
             }
+            return ensurePrivateDirExists(mFilesDir);
         }
-        return localFilesDir;
     }
 
     @Override
@@ -856,37 +846,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 (mDirsLock) {
+            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 (mDirsLock) {
+            if (mNoBackupFilesDir == null) {
+                mNoBackupFilesDir = new File(getDataDir(), "no_backup");
             }
+            return ensurePrivateDirExists(mNoBackupFilesDir);
         }
-        return localNoBackupFilesDir;
     }
 
     @Override
@@ -898,24 +876,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 (mDirsLock) {
+            File[] dirs = Environment.buildExternalStorageAppFilesDirs(getPackageName());
+            if (type != null) {
+                dirs = Environment.buildPaths(dirs, type);
             }
+            return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */);
         }
-        return localExternalFilesDirs;
     }
 
     @Override
@@ -927,51 +894,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 (mDirsLock) {
+            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 (mDirsLock) {
+            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 (mDirsLock) {
+            if (mCodeCacheDir == null) {
+                mCodeCacheDir = getCodeCacheDirBeforeBind(getDataDir());
             }
+            return ensurePrivateCacheDirExists(mCodeCacheDir, XATTR_INODE_CODE_CACHE);
         }
-        return localCodeCacheDir;
     }
 
     /**
@@ -992,37 +938,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 (mDirsLock) {
+            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 (mDirsLock) {
+            File[] dirs = Environment.buildExternalStorageAppMediaDirs(getPackageName());
+            return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */);
         }
-        return localExternalMediaDirs;
     }
 
     /**
@@ -1121,22 +1051,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 (mDirsLock) {
+            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/ForegroundServiceDelegationOptions.java b/core/java/android/app/ForegroundServiceDelegationOptions.java
index 875e01f..d6b6a58 100644
--- a/core/java/android/app/ForegroundServiceDelegationOptions.java
+++ b/core/java/android/app/ForegroundServiceDelegationOptions.java
@@ -92,6 +92,16 @@
      */
     public final @DelegationService int mDelegationService;
 
+    /**
+     * The optional notification Id of the foreground service delegation.
+     */
+    public final int mClientNotificationId;
+
+    /**
+     * The optional notification of the foreground service delegation.
+     */
+    public final @Nullable Notification mClientNotification;
+
     public ForegroundServiceDelegationOptions(int clientPid,
             int clientUid,
             @NonNull String clientPackageName,
@@ -100,6 +110,21 @@
             @NonNull String clientInstanceName,
             int foregroundServiceTypes,
             @DelegationService int delegationService) {
+        this(clientPid, clientUid, clientPackageName, clientAppThread, isSticky,
+                clientInstanceName, foregroundServiceTypes, delegationService,
+                0 /* notificationId */, null /* notification */);
+    }
+
+    public ForegroundServiceDelegationOptions(int clientPid,
+            int clientUid,
+            @NonNull String clientPackageName,
+            @NonNull IApplicationThread clientAppThread,
+            boolean isSticky,
+            @NonNull String clientInstanceName,
+            int foregroundServiceTypes,
+            @DelegationService int delegationService,
+            int clientNotificationId,
+            @Nullable Notification clientNotification) {
         mClientPid = clientPid;
         mClientUid = clientUid;
         mClientPackageName = clientPackageName;
@@ -108,6 +133,8 @@
         mClientInstanceName = clientInstanceName;
         mForegroundServiceTypes = foregroundServiceTypes;
         mDelegationService = delegationService;
+        mClientNotificationId = clientNotificationId;
+        mClientNotification = clientNotification;
     }
 
     /**
@@ -201,7 +228,8 @@
         int mClientPid; // The actual app PID
         int mClientUid; // The actual app UID
         String mClientPackageName; // The actual app's package name
-        int mClientNotificationId; // The actual app's notification
+        int mClientNotificationId; // The actual app's notification id
+        Notification mClientNotification; // The actual app's notification
         IApplicationThread mClientAppThread; // The actual app's app thread
         boolean mSticky; // Is it a sticky service
         String mClientInstanceName; // The delegation service instance name
@@ -233,10 +261,12 @@
         }
 
         /**
-         * Set the notification ID from the client app.
+         * Set the notification from the client app.
          */
-        public Builder setClientNotificationId(int clientNotificationId) {
+        public Builder setClientNotification(int clientNotificationId,
+                @Nullable Notification clientNotification) {
             mClientNotificationId = clientNotificationId;
+            mClientNotification = clientNotification;
             return this;
         }
 
@@ -291,7 +321,9 @@
                 mSticky,
                 mClientInstanceName,
                 mForegroundServiceTypes,
-                mDelegationService
+                mDelegationService,
+                mClientNotificationId,
+                mClientNotification
             );
         }
     }
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 fa4a400..66269a5 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -32,6 +32,7 @@
 import android.app.blob.BlobStoreManagerFrameworkInitializer;
 import android.app.contentsuggestions.ContentSuggestionsManager;
 import android.app.contentsuggestions.IContentSuggestionsManager;
+import android.app.contextualsearch.ContextualSearchManager;
 import android.app.ecm.EnhancedConfirmationFrameworkInitializer;
 import android.app.job.JobSchedulerFrameworkInitializer;
 import android.app.ondeviceintelligence.IOnDeviceIntelligenceManager;
@@ -1282,6 +1283,16 @@
                 }
             });
 
+        registerService(Context.CONTEXTUAL_SEARCH_SERVICE, ContextualSearchManager.class,
+                new CachedServiceFetcher<>() {
+                    @Override
+                    public ContextualSearchManager createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        IBinder b = ServiceManager.getService(Context.CONTEXTUAL_SEARCH_SERVICE);
+                        return b == null ? null : new ContextualSearchManager();
+                    }
+                });
+
         registerService(Context.APP_PREDICTION_SERVICE, AppPredictionManager.class,
                 new CachedServiceFetcher<AppPredictionManager>() {
             @Override
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index cb4ed058..5b28f50 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;
@@ -5952,7 +5952,8 @@
      * <p>
      * This method can be called on the {@link DevicePolicyManager} instance returned by
      * {@link #getParentProfileInstance(ComponentName)} in order to set a value on the parent
-     * profile.
+     * profile. This allows a profile wipe after too many incorrect device-unlock password have
+     * been entered on the parent profile even if each profile has a separate challenge.
      * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
      * password is always empty and this method has no effect - i.e. the policy is not set.
      *
@@ -9979,17 +9980,22 @@
     /**
      * Called by a profile owner or device owner or holder of the permission
      * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK_TASK}. to set a default activity
-     * that the system selects to handle intents that match the given {@link IntentFilter}.
+     * that the system selects to handle intents that match the given {@link IntentFilter} instead
+     * of showing the default disambiguation mechanism.
      * This activity will remain the default intent handler even if the set of potential event
      * handlers for the intent filter changes and if the intent preferences are reset.
      * <p>
-     * Note that the caller should still declare the activity in the manifest, the API just sets
-     * the activity to be the default one to handle the given intent filter.
+     * Note that the target application should still declare the activity in the manifest, the API
+     * just sets the activity to be the default one to handle the given intent filter.
      * <p>
      * The default disambiguation mechanism takes over if the activity is not installed (anymore).
      * When the activity is (re)installed, it is automatically reset as default intent handler for
      * the filter.
      * <p>
+     * Note that calling this API to set a default intent handler, only allow to avoid the default
+     * disambiguation mechanism. Implicit intents that do not trigger this mechanism (like invoking
+     * the browser) cannot be configured as they are controlled by other configurations.
+     * <p>
      * The calling device admin must be a profile owner or device owner. If it is not, a security
      * exception will be thrown.
      * <p>
@@ -17152,15 +17158,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/contextualsearch/ContextualSearchManager.java b/core/java/android/app/contextualsearch/ContextualSearchManager.java
new file mode 100644
index 0000000..693de21
--- /dev/null
+++ b/core/java/android/app/contextualsearch/ContextualSearchManager.java
@@ -0,0 +1,230 @@
+/*
+ * 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.contextualsearch;
+
+import static android.Manifest.permission.ACCESS_CONTEXTUAL_SEARCH;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.app.contextualsearch.flags.Flags;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.OutcomeReceiver;
+import android.os.ParcelableException;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+
+/**
+ * {@link ContextualSearchManager} is a system service to facilitate contextual search experience on
+ * configured Android devices.
+ * <p>
+ * This class lets
+ * <ul>
+ *   <li> a caller start contextual search by calling {@link #startContextualSearch} method.
+ *   <li> a handler request {@link ContextualSearchState} by calling the
+ *   {@link #getContextualSearchState} method.
+ * </ul>
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_ENABLE_SERVICE)
+public class ContextualSearchManager {
+
+    /**
+     * Key to get the entrypoint from the extras of the activity launched by contextual search.
+     * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
+     */
+    public static final String EXTRA_ENTRYPOINT =
+            "android.app.contextualsearch.extra.ENTRYPOINT";
+    /**
+     * Key to get the flag_secure value from the extras of the activity launched by contextual
+     * search. The value will be true if flag_secure is found in any of the visible activities.
+     * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
+     */
+    public static final String EXTRA_FLAG_SECURE_FOUND =
+            "android.app.contextualsearch.extra.FLAG_SECURE_FOUND";
+    /**
+     * Key to get the screenshot from the extras of the activity launched by contextual search.
+     * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
+     */
+    public static final String EXTRA_SCREENSHOT =
+            "android.app.contextualsearch.extra.SCREENSHOT";
+    /**
+     * Key to check whether managed profile is visible from the extras of the activity launched by
+     * contextual search. The value will be true if any one of the visible apps is managed.
+     * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
+     */
+    public static final String EXTRA_IS_MANAGED_PROFILE_VISIBLE =
+            "android.app.contextualsearch.extra.IS_MANAGED_PROFILE_VISIBLE";
+    /**
+     * Key to get the list of visible packages from the extras of the activity launched by
+     * contextual search.
+     * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
+     */
+    public static final String EXTRA_VISIBLE_PACKAGE_NAMES =
+            "android.app.contextualsearch.extra.VISIBLE_PACKAGE_NAMES";
+
+    /**
+     * Key to get the binder token from the extras of the activity launched by contextual search.
+     * This token is needed to invoke {@link #getContextualSearchState} method.
+     * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
+     */
+    public static final String EXTRA_TOKEN = "android.app.contextualsearch.extra.TOKEN";
+    /**
+     * Intent action for contextual search invocation. The app providing the contextual search
+     * experience must add this intent filter action to the activity it wants to be launched.
+     * <br>
+     * <b>Note</b> This activity must not be exported.
+     */
+    public static final String ACTION_LAUNCH_CONTEXTUAL_SEARCH =
+            "android.app.contextualsearch.action.LAUNCH_CONTEXTUAL_SEARCH";
+
+    /** Entrypoint to be used when a user long presses on the nav handle. */
+    public static final int ENTRYPOINT_LONG_PRESS_NAV_HANDLE = 1;
+    /** Entrypoint to be used when a user long presses on the home button. */
+    public static final int ENTRYPOINT_LONG_PRESS_HOME = 2;
+    /** Entrypoint to be used when a user long presses on the overview button. */
+    public static final int ENTRYPOINT_LONG_PRESS_OVERVIEW = 3;
+    /** Entrypoint to be used when a user presses the action button in overview. */
+    public static final int ENTRYPOINT_OVERVIEW_ACTION = 4;
+    /** Entrypoint to be used when a user presses the context menu button in overview. */
+    public static final int ENTRYPOINT_OVERVIEW_MENU = 5;
+    /** Entrypoint to be used by system actions like TalkBack, Accessibility etc. */
+    public static final int ENTRYPOINT_SYSTEM_ACTION = 9;
+    /** Entrypoint to be used when a user long presses on the meta key. */
+    public static final int ENTRYPOINT_LONG_PRESS_META = 10;
+    /**
+     * The {@link Entrypoint} annotation is used to standardize the entrypoints supported by
+     * {@link #startContextualSearch} method.
+     *
+     * @hide
+     */
+    @IntDef(prefix = {"ENTRYPOINT_"}, value = {
+            ENTRYPOINT_LONG_PRESS_NAV_HANDLE,
+            ENTRYPOINT_LONG_PRESS_HOME,
+            ENTRYPOINT_LONG_PRESS_OVERVIEW,
+            ENTRYPOINT_OVERVIEW_ACTION,
+            ENTRYPOINT_OVERVIEW_MENU,
+            ENTRYPOINT_SYSTEM_ACTION,
+            ENTRYPOINT_LONG_PRESS_META
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Entrypoint {
+    }
+    private static final String TAG = ContextualSearchManager.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    private final IContextualSearchManager mService;
+
+    /** @hide */
+    public ContextualSearchManager() {
+        if (DEBUG) Log.d(TAG, "ContextualSearchManager created");
+        IBinder b = ServiceManager.getService(Context.CONTEXTUAL_SEARCH_SERVICE);
+        mService = IContextualSearchManager.Stub.asInterface(b);
+    }
+
+    /**
+     * Used to start contextual search.
+     * <p>
+     *     When {@link #startContextualSearch} is called, the system server does the following:
+     *     <ul>
+     *         <li>Resolves the activity using the package name and intent filter. The package name
+     *             is fetched from the config specified in ContextualSearchManagerService.
+     *             The activity must have ACTION_LAUNCH_CONTEXTUAL_SEARCH specified in its manifest.
+     *         <li>Puts the required extras in the launch intent.
+     *         <li>Launches the activity.
+     *     </ul>
+     * </p>
+     *
+     * @param entrypoint the invocation entrypoint
+     */
+    @RequiresPermission(ACCESS_CONTEXTUAL_SEARCH)
+    public void startContextualSearch(@Entrypoint int entrypoint) {
+        if (DEBUG) Log.d(TAG, "startContextualSearch for entrypoint: " + entrypoint);
+        try {
+            mService.startContextualSearch(entrypoint);
+        } catch (RemoteException e) {
+            if (DEBUG) Log.d(TAG, "Failed to startContextualSearch", e);
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the {@link ContextualSearchState} to the handler via the provided callback.
+     *
+     * @param token The caller is expected to get the token from the launch extras of the handling
+     *              activity using {@link Bundle#getIBinder} with {@link #EXTRA_TOKEN} key.
+     *              <br>
+     *              <b>Note</b> This token is for one time use only. Subsequent uses will invoke
+     *              callback's {@link OutcomeReceiver#onError}.
+     * @param executor The executor which will be used to invoke the callback.
+     * @param callback The callback which will be used to return {@link ContextualSearchState}
+     *                 if/when it is available via {@link OutcomeReceiver#onResult}. It will also be
+     *                 used to return errors via {@link OutcomeReceiver#onError}.
+     */
+    public void getContextualSearchState(@NonNull IBinder token,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OutcomeReceiver<ContextualSearchState, Throwable> callback) {
+        if (DEBUG) Log.d(TAG, "getContextualSearchState for token:" + token);
+        try {
+            final CallbackWrapper wrapper = new CallbackWrapper(executor, callback);
+            mService.getContextualSearchState(token, wrapper);
+        } catch (RemoteException e) {
+            if (DEBUG) Log.d(TAG, "Failed to getContextualSearchState", e);
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    private static class CallbackWrapper extends IContextualSearchCallback.Stub {
+        private final OutcomeReceiver<ContextualSearchState, Throwable> mCallback;
+        private final Executor mExecutor;
+
+        CallbackWrapper(@NonNull Executor callbackExecutor,
+                        @NonNull OutcomeReceiver<ContextualSearchState, Throwable> callback) {
+            mCallback = callback;
+            mExecutor = callbackExecutor;
+        }
+
+        @Override
+        public void onResult(ContextualSearchState state) {
+            Binder.withCleanCallingIdentity(() -> {
+                if (DEBUG) Log.d(TAG, "onResult state:" + state);
+                mExecutor.execute(() -> mCallback.onResult(state));
+            });
+        }
+
+        @Override
+        public void onError(ParcelableException error) {
+            Binder.withCleanCallingIdentity(() -> {
+                if (DEBUG) Log.w(TAG, "onError", error);
+                mExecutor.execute(() -> mCallback.onError(error));
+            });
+        }
+    }
+}
diff --git a/core/java/android/app/contextualsearch/ContextualSearchState.aidl b/core/java/android/app/contextualsearch/ContextualSearchState.aidl
new file mode 100644
index 0000000..7f64484
--- /dev/null
+++ b/core/java/android/app/contextualsearch/ContextualSearchState.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2021, 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.contextualsearch;
+
+parcelable ContextualSearchState;
\ No newline at end of file
diff --git a/core/java/android/app/contextualsearch/ContextualSearchState.java b/core/java/android/app/contextualsearch/ContextualSearchState.java
new file mode 100644
index 0000000..5c04bc89
--- /dev/null
+++ b/core/java/android/app/contextualsearch/ContextualSearchState.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.contextualsearch;
+
+import android.annotation.FlaggedApi;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.assist.AssistContent;
+import android.app.assist.AssistStructure;
+import android.app.contextualsearch.flags.Flags;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+/**
+ * {@link ContextualSearchState} contains additional data a contextual search handler can request
+ * via {@link ContextualSearchManager#getContextualSearchState} method.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_ENABLE_SERVICE)
+@SystemApi
+public final class ContextualSearchState implements Parcelable {
+    private final @NonNull Bundle mExtras;
+    private final @Nullable AssistStructure mStructure;
+    private final @Nullable AssistContent mContent;
+
+    public ContextualSearchState(@Nullable AssistStructure structure,
+            @Nullable AssistContent content, @NonNull Bundle extras) {
+        mStructure = structure;
+        mContent = content;
+        mExtras = extras;
+    }
+
+    private ContextualSearchState(Parcel source) {
+        this.mStructure = source.readTypedObject(AssistStructure.CREATOR);
+        this.mContent = source.readTypedObject(AssistContent.CREATOR);
+        Bundle extras = source.readBundle(getClass().getClassLoader());
+        this.mExtras = extras != null ? extras : Bundle.EMPTY;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeTypedObject(this.mStructure, flags);
+        dest.writeTypedObject(this.mContent, flags);
+        dest.writeBundle(this.mExtras);
+    }
+
+    /** Gets an instance of {@link AssistContent}. */
+    @Nullable
+    public AssistContent getContent() {
+        return mContent;
+    }
+
+    /** Gets an instance of {@link AssistStructure}. */
+    @Nullable
+    public AssistStructure getStructure() {
+        return mStructure;
+    }
+
+    /**
+     * Gets an instance of {@link Bundle} containing the extras added by the system server.
+     * The contents of this bundle vary by usecase. When Contextual is invoked via Launcher, this
+     * bundle is empty.
+     */
+    @NonNull
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    @NonNull
+    public static final Creator<ContextualSearchState> CREATOR = new Creator<>() {
+        @Override
+        public ContextualSearchState createFromParcel(Parcel source) {
+            return new ContextualSearchState(source);
+        }
+
+        @Override
+        public ContextualSearchState[] newArray(int size) {
+            return new ContextualSearchState[size];
+        }
+    };
+}
diff --git a/core/java/android/app/contextualsearch/IContextualSearchCallback.aidl b/core/java/android/app/contextualsearch/IContextualSearchCallback.aidl
new file mode 100644
index 0000000..5fe5fd2
--- /dev/null
+++ b/core/java/android/app/contextualsearch/IContextualSearchCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 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.contextualsearch;
+
+import android.os.ParcelableException;
+import android.app.contextualsearch.ContextualSearchState;
+/**
+ * @hide
+ */
+oneway interface IContextualSearchCallback {
+    void onResult(in ContextualSearchState state);
+    void onError(in ParcelableException error);
+}
diff --git a/core/java/android/app/contextualsearch/IContextualSearchManager.aidl b/core/java/android/app/contextualsearch/IContextualSearchManager.aidl
new file mode 100644
index 0000000..1735a71
--- /dev/null
+++ b/core/java/android/app/contextualsearch/IContextualSearchManager.aidl
@@ -0,0 +1,11 @@
+package android.app.contextualsearch;
+
+
+import android.app.contextualsearch.IContextualSearchCallback;
+/**
+ * @hide
+ */
+oneway interface IContextualSearchManager {
+  void startContextualSearch(int entrypoint);
+  void getContextualSearchState(in IBinder token, in IContextualSearchCallback callback);
+}
diff --git a/core/java/android/app/contextualsearch/flags.aconfig b/core/java/android/app/contextualsearch/flags.aconfig
new file mode 100644
index 0000000..5ab0762
--- /dev/null
+++ b/core/java/android/app/contextualsearch/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.app.contextualsearch.flags"
+
+flag {
+  name: "enable_service"
+  namespace: "machine_learning"
+  description: "Flag to enable the service"
+  bug: "309689654"
+}
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/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 9573e6f..df6d2a6 100644
--- a/core/java/android/app/wearable/WearableSensingManager.java
+++ b/core/java/android/app/wearable/WearableSensingManager.java
@@ -246,7 +246,10 @@
      * @param executor Executor on which to run the consumer callback
      * @param statusConsumer A consumer that handles the status codes, which is returned
      *                 right after the call.
+     * @deprecated Use {@link #provideConnection(ParcelFileDescriptor, Executor, Consumer)} instead
+     *     to provide a remote wearable device connection to the WearableSensingService
      */
+    @Deprecated
     @RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)
     public void provideDataStream(
             @NonNull ParcelFileDescriptor parcelFileDescriptor,
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 7f2ec53..1653bf5 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5396,6 +5396,19 @@
     public static final String SMARTSPACE_SERVICE = "smartspace";
 
     /**
+     * Used for getting the contextual search service.
+     *
+     * <p><b>NOTE: </b> this service is optional; callers of
+     * {@code Context.getSystemServiceName(CONTEXTUAL_SEARCH_SERVICE)} must check for {@code null}.
+     *
+     * @hide
+     * @see #getSystemService(String)
+     */
+    @FlaggedApi(android.app.contextualsearch.flags.Flags.FLAG_ENABLE_SERVICE)
+    @SystemApi
+    public static final String CONTEXTUAL_SEARCH_SERVICE = "contextual_search";
+
+    /**
      * Used for getting the cloudsearch service.
      *
      * <p><b>NOTE: </b> this service is optional; callers of
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 443aadd..8bc5e8c 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -6129,10 +6129,14 @@
      * the selection changes made by the user.
      * Applications may implement this method to change any of the following Chooser arguments by
      * returning new values in the result bundle:
-     * {@link #EXTRA_CHOOSER_TARGETS}, {@link #EXTRA_ALTERNATE_INTENTS},
+     * {@link #EXTRA_CHOOSER_TARGETS},
+     * {@link #EXTRA_ALTERNATE_INTENTS},
      * {@link #EXTRA_CHOOSER_CUSTOM_ACTIONS},
      * {@link #EXTRA_CHOOSER_MODIFY_SHARE_ACTION},
-     * {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER}.</p>
+     * {@link #EXTRA_METADATA_TEXT},
+     * {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER},
+     * {@link #EXTRA_CHOOSER_RESULT_INTENT_SENDER}.
+     * </p>
      */
     @FlaggedApi(android.service.chooser.Flags.FLAG_CHOOSER_PAYLOAD_TOGGLING)
     public static final String EXTRA_CHOOSER_ADDITIONAL_CONTENT_URI =
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/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..a8d54ed 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -16,7 +16,7 @@
 
 package android.hardware.biometrics;
 
-import static android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO;
+import static android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED;
 import static android.Manifest.permission.TEST_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
@@ -135,7 +135,7 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface DismissedReason {}
 
-    private static class ButtonInfo {
+    static class ButtonInfo {
         Executor executor;
         DialogInterface.OnClickListener listener;
         ButtonInfo(Executor ex, DialogInterface.OnClickListener l) {
@@ -150,6 +150,7 @@
     public static class Builder {
         private PromptInfo mPromptInfo;
         private ButtonInfo mNegativeButtonInfo;
+        private ButtonInfo mContentViewMoreOptionsButtonInfo;
         private Context mContext;
         private IAuthService mService;
 
@@ -175,7 +176,7 @@
          * @return This builder.
          */
         @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-        @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO)
+        @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
         @NonNull
         public BiometricPrompt.Builder setLogoRes(@DrawableRes int logoRes) {
             mPromptInfo.setLogoRes(logoRes);
@@ -194,7 +195,7 @@
          * @return This builder.
          */
         @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-        @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO)
+        @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
         @NonNull
         public BiometricPrompt.Builder setLogoBitmap(@NonNull Bitmap logoBitmap) {
             mPromptInfo.setLogoBitmap(logoBitmap);
@@ -213,7 +214,7 @@
          * @return This builder.
          */
         @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-        @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO)
+        @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
         @NonNull
         public BiometricPrompt.Builder setLogoDescription(@NonNull String logoDescription) {
             mPromptInfo.setLogoDescription(logoDescription);
@@ -295,12 +296,18 @@
          * called.
          *
          * @param view The customized view information.
-         * @return This builder.re
+         * @return This builder.
          */
         @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
         @NonNull
         public BiometricPrompt.Builder setContentView(@NonNull PromptContentView view) {
             mPromptInfo.setContentView(view);
+
+            if (mPromptInfo.isContentViewMoreOptionsButtonUsed()) {
+                mContentViewMoreOptionsButtonInfo =
+                        ((PromptContentViewWithMoreOptionsButton) view).getButtonInfo();
+            }
+
             return this;
         }
 
@@ -619,7 +626,8 @@
             }
             mService = (mService == null) ? IAuthService.Stub.asInterface(
                     ServiceManager.getService(Context.AUTH_SERVICE)) : mService;
-            return new BiometricPrompt(mContext, mPromptInfo, mNegativeButtonInfo, mService);
+            return new BiometricPrompt(mContext, mPromptInfo, mNegativeButtonInfo,
+                    mContentViewMoreOptionsButtonInfo, mService);
         }
     }
 
@@ -646,6 +654,9 @@
     private final IAuthService mService;
     private final PromptInfo mPromptInfo;
     private final ButtonInfo mNegativeButtonInfo;
+    // TODO(b/328843028): add callback onContentViewMoreOptionsButtonClicked() in
+    //  IBiometricServiceReceiver.
+    private final ButtonInfo mContentViewMoreOptionsButtonInfo;
 
     private CryptoObject mCryptoObject;
     private Executor mExecutor;
@@ -751,10 +762,11 @@
     private boolean mIsPromptShowing;
 
     private BiometricPrompt(Context context, PromptInfo promptInfo, ButtonInfo negativeButtonInfo,
-            IAuthService service) {
+            ButtonInfo contentViewMoreOptionsButtonInfo, IAuthService service) {
         mContext = context;
         mPromptInfo = promptInfo;
         mNegativeButtonInfo = negativeButtonInfo;
+        mContentViewMoreOptionsButtonInfo = contentViewMoreOptionsButtonInfo;
         mService = service;
         mIsPromptShowing = false;
     }
@@ -766,7 +778,7 @@
      * @return The drawable resource of the logo, or -1 if the prompt has no logo resource set.
      */
     @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-    @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO)
+    @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
     @DrawableRes
     public int getLogoRes() {
         return mPromptInfo.getLogoRes();
@@ -779,7 +791,7 @@
      * @return The logo bitmap of the prompt, or null if the prompt has no logo bitmap set.
      */
     @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-    @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO)
+    @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
     @Nullable
     public Bitmap getLogoBitmap() {
         return mPromptInfo.getLogoBitmap();
@@ -794,7 +806,7 @@
      * set.
      */
     @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-    @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO)
+    @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
     @Nullable
     public String getLogoDescription() {
         return mPromptInfo.getLogoDescription();
@@ -932,14 +944,29 @@
      * @see Builder#setAllowedAuthenticators(int)
      */
     public static final class CryptoObject extends android.hardware.biometrics.CryptoObject {
+        /**
+         * Create from a {@link Signature} object.
+         *
+         * @param signature a {@link Signature} object.
+         */
         public CryptoObject(@NonNull Signature signature) {
             super(signature);
         }
 
+        /**
+         * Create from a {@link Cipher} object.
+         *
+         * @param cipher a {@link Cipher} object.
+         */
         public CryptoObject(@NonNull Cipher cipher) {
             super(cipher);
         }
 
+        /**
+         * Create from a {@link Mac} object.
+         *
+         * @param mac a {@link Mac} object.
+         */
         public CryptoObject(@NonNull Mac mac) {
             super(mac);
         }
@@ -955,16 +982,37 @@
             super(credential);
         }
 
+        /**
+         * Create from a {@link PresentationSession} object.
+         *
+         * @param session a {@link PresentationSession} object.
+         */
         public CryptoObject(@NonNull PresentationSession session) {
             super(session);
         }
 
+        /**
+         * Create from a {@link KeyAgreement} object.
+         *
+         * @param keyAgreement a {@link KeyAgreement} object.
+         */
         @FlaggedApi(FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT)
         public CryptoObject(@NonNull KeyAgreement keyAgreement) {
             super(keyAgreement);
         }
 
         /**
+         * Create from an operation handle.
+         * @see CryptoObject#getOperationHandle()
+         *
+         * @param operationHandle the operation handle associated with this object.
+         */
+        @FlaggedApi(FLAG_GET_OP_ID_CRYPTO_OBJECT)
+        public CryptoObject(long operationHandle) {
+            super(operationHandle);
+        }
+
+        /**
          * Get {@link Signature} object.
          * @return {@link Signature} object or null if this doesn't contain one.
          */
@@ -1016,7 +1064,20 @@
         }
 
         /**
-         * Get the operation handle associated with this object or 0 if none.
+         * Returns the {@code operationHandle} associated with this object or 0 if none.
+         * The {@code operationHandle} is the underlying identifier associated with
+         * the {@code CryptoObject}.
+         *
+         * <p> The {@code operationHandle} can be used to reconstruct a {@code CryptoObject}
+         * instance. This is useful for any cross-process communication as the {@code CryptoObject}
+         * class is not {@link android.os.Parcelable}. Hence, if the {@code CryptoObject} is
+         * constructed in one process, and needs to be propagated to another process,
+         * before calling the
+         * {@link BiometricPrompt#authenticate(CryptoObject, CancellationSignal, Executor,
+         * AuthenticationCallback)} API in the second process, the recommendation is to retrieve the
+         * {@code operationHandle} using this API, and then reconstruct the
+         * {@code CryptoObject}using the constructor that takes in an {@code operationHandle}, and
+         * pass that in to the {@code authenticate} API mentioned above.
          */
         @FlaggedApi(FLAG_GET_OP_ID_CRYPTO_OBJECT)
         public long getOperationHandle() {
diff --git a/core/java/android/hardware/biometrics/CryptoObject.java b/core/java/android/hardware/biometrics/CryptoObject.java
index 8d3ea3f..81b4c21 100644
--- a/core/java/android/hardware/biometrics/CryptoObject.java
+++ b/core/java/android/hardware/biometrics/CryptoObject.java
@@ -97,6 +97,10 @@
         mCrypto = keyAgreement;
     }
 
+    public CryptoObject(long operationHandle) {
+        mCrypto = operationHandle;
+    }
+
     /**
      * Get {@link Signature} object.
      * @return {@link Signature} object or null if this doesn't contain one.
@@ -157,6 +161,8 @@
     public long getOpId() {
         if (mCrypto == null) {
             return 0;
+        } else if (mCrypto instanceof Long) {
+            return (long) mCrypto;
         } else if (mCrypto instanceof IdentityCredential) {
             return ((IdentityCredential) mCrypto).getCredstoreOperationHandle();
         } else if (mCrypto instanceof PresentationSession) {
diff --git a/core/java/android/hardware/biometrics/PromptContentViewParcelable.java b/core/java/android/hardware/biometrics/PromptContentViewParcelable.java
index 43b965b..b5982d4 100644
--- a/core/java/android/hardware/biometrics/PromptContentViewParcelable.java
+++ b/core/java/android/hardware/biometrics/PromptContentViewParcelable.java
@@ -26,5 +26,5 @@
  */
 @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
 sealed interface PromptContentViewParcelable extends PromptContentView, Parcelable
-        permits PromptVerticalListContentView {
+        permits PromptVerticalListContentView, PromptContentViewWithMoreOptionsButton {
 }
diff --git a/core/java/android/hardware/biometrics/PromptContentViewWithMoreOptionsButton.java b/core/java/android/hardware/biometrics/PromptContentViewWithMoreOptionsButton.java
new file mode 100644
index 0000000..9ebfa8f
--- /dev/null
+++ b/core/java/android/hardware/biometrics/PromptContentViewWithMoreOptionsButton.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.hardware.biometrics;
+
+import static android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED;
+import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.content.DialogInterface;
+import android.hardware.biometrics.BiometricPrompt.ButtonInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Contains the information of the template of content view with a more options button for Biometric
+ * Prompt.
+ * This button should be used to provide more options for sign in or other purposes, such as when a
+ * user needs to select between multiple app-specific accounts or profiles that are available for
+ * sign in. This is not common and apps should avoid using it if there is only one choice available
+ * or if the user has already selected the appropriate account to use before invoking
+ * BiometricPrompt because it will create additional steps that the user must navigate through.
+ * Clicking the more options button will dismiss the prompt, provide the app an opportunity to ask
+ * the user for the correct account, &finally allow the app to decide how to proceed once selected.
+ * <p>
+ * Here's how you'd set a <code>PromptContentViewWithMoreOptionsButton</code> on a Biometric Prompt:
+ * <pre class="prettyprint">
+ * BiometricPrompt biometricPrompt = new BiometricPrompt.Builder(...)
+ *     .setTitle(...)
+ *     .setSubTitle(...)
+ *     .setContentView(new PromptContentViewWithMoreOptionsButton.Builder()
+ *         .setDescription("test description")
+ *         .setMoreOptionsButtonListener(executor, listener)
+ *         .build())
+ *     .build();
+ * </pre>
+ */
+@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+public final class PromptContentViewWithMoreOptionsButton implements PromptContentViewParcelable {
+
+    private final String mDescription;
+    private DialogInterface.OnClickListener mListener;
+    private ButtonInfo mButtonInfo;
+
+    private PromptContentViewWithMoreOptionsButton(
+            @NonNull String description, @NonNull @CallbackExecutor Executor executor,
+            @NonNull DialogInterface.OnClickListener listener) {
+        mDescription = description;
+        mListener = listener;
+        mButtonInfo = new ButtonInfo(executor, listener);
+    }
+
+    private PromptContentViewWithMoreOptionsButton(Parcel in) {
+        mDescription = in.readString();
+    }
+
+    /**
+     * Gets the description for the content view, as set by
+     * {@link PromptContentViewWithMoreOptionsButton.Builder#setDescription(String)}.
+     *
+     * @return The description for the content view, or null if the content view has no description.
+     */
+    @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
+    @Nullable
+    public String getDescription() {
+        return mDescription;
+    }
+
+    /**
+     * Gets the click listener for the more options button on the content view, as set by
+     * {@link PromptContentViewWithMoreOptionsButton.Builder#setMoreOptionsButtonListener(Executor,
+     * DialogInterface.OnClickListener)}.
+     *
+     * @return The click listener for the more options button on the content view.
+     */
+    @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
+    @NonNull
+    public DialogInterface.OnClickListener getMoreOptionsButtonListener() {
+        return mListener;
+    }
+
+    ButtonInfo getButtonInfo() {
+        return mButtonInfo;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mDescription);
+    }
+
+    /**
+     * @see Parcelable.Creator
+     */
+    @NonNull
+    public static final Creator<PromptContentViewWithMoreOptionsButton> CREATOR = new Creator<>() {
+        @Override
+        public PromptContentViewWithMoreOptionsButton createFromParcel(Parcel in) {
+            return new PromptContentViewWithMoreOptionsButton(in);
+        }
+
+        @Override
+        public PromptContentViewWithMoreOptionsButton[] newArray(int size) {
+            return new PromptContentViewWithMoreOptionsButton[size];
+        }
+    };
+
+    public static final class Builder {
+        private String mDescription;
+        private Executor mExecutor;
+        private DialogInterface.OnClickListener mListener;
+
+        /**
+         * Optional: Sets a description that will be shown on the content view.  Note that there are
+         * limits on the number of characters allowed for description.
+         *
+         * @param description The description to display.
+         * @return This builder.
+         */
+        @NonNull
+        @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
+        public Builder setDescription(@NonNull String description) {
+            mDescription = description;
+            return this;
+        }
+
+        /**
+         * Required: Sets the executor and click listener for the more options button on the
+         * prompt content.
+         * This button should be used to provide more options for sign in or other purposes, such
+         * as when a user needs to select between multiple app-specific accounts or profiles that
+         * are available for sign in. This is not common and apps should avoid using it if there
+         * is only one choice available or if the user has already selected the appropriate
+         * account to use before invoking BiometricPrompt because it will create additional steps
+         * that the user must navigate through. Clicking the more options button will dismiss the
+         * prompt, provide the app an opportunity to ask the user for the correct account, &finally
+         * allow the app to decide how to proceed once selected.
+         *
+         * @param executor Executor that will be used to run the on click callback.
+         * @param listener Listener containing a callback to be run when the button is pressed.
+         * @return This builder.
+         */
+        @NonNull
+        @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
+        public Builder setMoreOptionsButtonListener(@NonNull @CallbackExecutor Executor executor,
+                @NonNull DialogInterface.OnClickListener listener) {
+            if (executor == null) {
+                throw new IllegalArgumentException("Executor must not be null");
+            }
+            if (listener == null) {
+                throw new IllegalArgumentException("Listener must not be null");
+            }
+            mExecutor = executor;
+            mListener = listener;
+            return this;
+        }
+
+
+        /**
+         * Creates a {@link PromptContentViewWithMoreOptionsButton}.
+         *
+         * @return An instance of {@link PromptContentViewWithMoreOptionsButton}.
+         */
+        @NonNull
+        @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
+        public PromptContentViewWithMoreOptionsButton build() {
+            if (mListener == null) {
+                throw new IllegalArgumentException(
+                        "The listener of more options button on prompt content must be set if "
+                                + "PromptContentViewWithMoreOptionsButton is used.");
+            }
+            return new PromptContentViewWithMoreOptionsButton(mDescription, mExecutor, mListener);
+        }
+    }
+}
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index 8bb9585..8143213 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -172,7 +172,7 @@
     }
 
     /**
-     * Returns whether SET_BIOMETRIC_DIALOG_LOGO is contained.
+     * Returns whether SET_BIOMETRIC_DIALOG_ADVANCED is contained.
      */
     public boolean containsSetLogoApiConfigurations() {
         if (mLogoRes != -1) {
@@ -191,6 +191,15 @@
     public boolean shouldUseParentProfileForDeviceCredential() {
         return mUseParentProfileForDeviceCredential;
     }
+
+    /**
+     * Returns if the PromptContentViewWithMoreOptionsButton is set.
+     */
+    public boolean isContentViewMoreOptionsButtonUsed() {
+        return Flags.customBiometricPrompt() && mContentView != null
+                && mContentView instanceof PromptContentViewWithMoreOptionsButton;
+    }
+
     // LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/BiometricPrompt.java)
 
     // Setters
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 991bade..ec9b013 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -585,6 +585,11 @@
      * <p>Configuring a session with an empty or null list will close the current session, if
      * any. This can be used to release the current session's target surfaces for another use.</p>
      *
+     * <p>This function throws an {@code IllegalArgumentException} if called with a
+     * SessionConfiguration lacking state callbacks or valid output surfaces. The only exceptions
+     * are deferred SurfaceView or SurfaceTexture outputs. See {@link
+     * OutputConfiguration#OutputConfiguration(Size, Class)} for details.</p>
+     *
      * <h3>Regular capture</h3>
      *
      * <p>While any of the sizes from {@link StreamConfigurationMap#getOutputSizes} can be used when
@@ -1675,19 +1680,32 @@
          *
          * <p><b>IMPORTANT:</b></p>
          * <ul>
-         * <li>If a feature support can be queried via
+         * <li>If feature support can be queried via
          * {@link CameraCharacteristics#SCALER_MANDATORY_STREAM_COMBINATIONS} or
          * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP}, applications should
-         * directly use it rather than calling this function as: (1) using
-         * {@code CameraCharacteristics} is more efficient, and (2) calling this function with on
-         * non-supported devices will throw a {@link UnsupportedOperationException}.
+         * directly use that route rather than calling this function as: (1) using
+         * {@code CameraCharacteristics} is more efficient, and (2) calling this function with
+         * certain non-supported features will throw a {@link IllegalArgumentException}.</li>
          *
-         * <li>To minimize latency of {@link SessionConfiguration} creation, applications can
-         * use deferred surfaces for SurfaceView and SurfaceTexture to avoid waiting for UI
-         * creation before setting up the camera. For {@link android.media.MediaRecorder} and
-         * {@link android.media.MediaCodec} uses, applications can use {@code ImageReader} with
-         * {@link android.hardware.HardwareBuffer#USAGE_VIDEO_ENCODE}. The lightweight nature of
-         * {@code ImageReader} helps minimize the latency cost.
+         * <li>To minimize {@link SessionConfiguration} creation latency due to its dependency on
+         * output surfaces, the application can call this method before acquiring valid
+         * {@link android.view.SurfaceView}, {@link android.graphics.SurfaceTexture},
+         * {@link android.media.MediaRecorder}, {@link android.media.MediaCodec}, or {@link
+         * android.media.ImageReader} surfaces. For {@link android.view.SurfaceView},
+         * {@link android.graphics.SurfaceTexture}, {@link android.media.MediaRecorder}, and
+         * {@link android.media.MediaCodec}, the application can call
+         * {@link OutputConfiguration#OutputConfiguration(Size, Class)}. For {@link
+         * android.media.ImageReader}, the application can call {@link
+         * OutputConfiguration#OutputConfiguration(int, Size)}, {@link
+         * OutputConfiguration#OutputConfiguration(int, int, Size)}, {@link
+         * OutputConfiguration#OutputConfiguration(int, Size, long)}, or {@link
+         * OutputConfiguration#OutputConfiguration(int, int, Size, long)}. The {@link
+         * SessionConfiguration} can then be created using the OutputConfiguration objects and
+         * be used to query whether it's supported by the camera device. To create the
+         * CameraCaptureSession, the application still needs to make sure all output surfaces
+         * are added via {@link OutputConfiguration#addSurfaces} with the exception of deferred
+         * surfaces for {@link android.view.SurfaceView} and
+         * {@link android.graphics.SurfaceTexture}.</li>
          * </ul>
          *
          * @return {@code true} if the given session configuration is supported by the camera
@@ -1706,8 +1724,8 @@
                 @NonNull SessionConfiguration config) throws CameraAccessException;
 
         /**
-         * <p>Get camera characteristics for a particular session configuration for this camera
-         * device</p>
+         * Get camera characteristics for a particular session configuration for this camera
+         * device.
          *
          * <p>The camera characteristics returned by this method are different from those returned
          * from {@link CameraManager#getCameraCharacteristics}. The characteristics returned here
@@ -1718,6 +1736,24 @@
          * <p>Other than that, the characteristics returned here can be used in the same way as
          * those returned from {@link CameraManager#getCameraCharacteristics}.</p>
          *
+         * <p>To optimize latency, the application can call this method before acquiring valid
+         * {@link android.view.SurfaceView}, {@link android.graphics.SurfaceTexture},
+         * {@link android.media.MediaRecorder}, {@link android.media.MediaCodec}, or {@link
+         * android.media.ImageReader} surfaces. For {@link android.view.SurfaceView},
+         * {@link android.graphics.SurfaceTexture}, {@link android.media.MediaRecorder}, and
+         * {@link android.media.MediaCodec}, the application can call
+         * {@link OutputConfiguration#OutputConfiguration(Size, Class)}. For {@link
+         * android.media.ImageReader}, the application can call {@link
+         * OutputConfiguration#OutputConfiguration(int, Size)}, {@link
+         * OutputConfiguration#OutputConfiguration(int, int, Size)}, {@link
+         * OutputConfiguration#OutputConfiguration(int, Size, long)}, or {@link
+         * OutputConfiguration#OutputConfiguration(int, int, Size, long)}. The {@link
+         * SessionConfiguration} can then be created using the OutputConfiguration objects and
+         * be used for this function. To create the CameraCaptureSession, the application still
+         * needs to make sure all output surfaces are added via {@link
+         * OutputConfiguration#addSurfaces} with the exception of deferred surfaces for {@link
+         * android.view.SurfaceView} and {@link android.graphics.SurfaceTexture}.</p>
+         *
          * @param sessionConfig The session configuration for which characteristics are fetched.
          * @return CameraCharacteristics specific to a given session configuration.
          *
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 6962811..eb644e8 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -229,9 +229,10 @@
                                                      StreamConfigurationMap streamMap) {
         ArrayList<Size> ret = getSupportedSizes(sizesList, format);
 
-        if (format == ImageFormat.JPEG || format == ImageFormat.YUV_420_888) {
+        if (format == ImageFormat.JPEG || format == ImageFormat.YUV_420_888 ||
+                format == ImageFormat.PRIVATE) {
             // 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
+            // camera advertised sizes for JPEG, YUV_420_888 and PRIVATE in case it doesn't return
             // a valid non-empty size list.
             Size[] supportedSizes = streamMap.getOutputSizes(format);
             if ((ret.isEmpty()) && (supportedSizes != null)) {
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/MultiResolutionImageReader.java b/core/java/android/hardware/camera2/MultiResolutionImageReader.java
index 8a18a0d..116928b 100644
--- a/core/java/android/hardware/camera2/MultiResolutionImageReader.java
+++ b/core/java/android/hardware/camera2/MultiResolutionImageReader.java
@@ -16,6 +16,8 @@
 
 package android.hardware.camera2;
 
+import static com.android.internal.util.Preconditions.checkNotNull;
+
 import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
 import android.annotation.IntRange;
@@ -26,20 +28,15 @@
 import android.graphics.ImageFormat.Format;
 import android.hardware.HardwareBuffer;
 import android.hardware.HardwareBuffer.Usage;
+import android.hardware.camera2.params.MultiResolutionStreamInfo;
 import android.media.Image;
 import android.media.ImageReader;
-import android.hardware.camera2.params.MultiResolutionStreamInfo;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Log;
+import android.util.Size;
 import android.view.Surface;
 
 import com.android.internal.camera.flags.Flags;
 
-import java.nio.NioUtils;
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.List;
 import java.util.concurrent.Executor;
 
 /**
@@ -351,6 +348,52 @@
     }
 
     /**
+     * Get the internal ImageReader surface based on configured size and physical camera Id.
+     *
+     * <p>The {@code configuredSize} and {@code physicalCameraId} parameters must match one of the
+     * MultiResolutionStreamInfo used to create this {@link MultiResolutionImageReader}.</p>
+     *
+     * <p>The Surface returned from this function isn't meant to be used directly as part of a
+     * {@link CaptureRequest}. It should instead be used for creating an OutputConfiguration
+     * before session creation. See {@link OutputConfiguration#setSurfacesForMultiResolutionOutput}
+     * for details. For {@link CaptureRequest}, use {@link #getSurface()} instead.</p>
+     *
+     * <p>Please note that holding on to the Surface objects returned by this method is not enough
+     * to keep their parent MultiResolutionImageReaders from being reclaimed. In that sense, a
+     * Surface acts like a {@link java.lang.ref.WeakReference weak reference} to the
+     * MultiResolutionImageReader that provides it.</p>
+     *
+     * @param configuredSize The configured size corresponding to one of the internal ImageReader.
+     * @param physicalCameraId The physical camera Id the internal ImageReader targets for. If
+     *                         the ImageReader is not targeting a physical camera of a logical
+     *                         multi-camera, this parameter is set to "".
+     *
+     * @return The {@link Surface} of the internal ImageReader corresponding to the provided
+     *         configured size and physical camera Id.
+     *
+     * @throws IllegalArgumentException If {@code configuredSize} is {@code null}, or the ({@code
+     *                                  configuredSize} and {@code physicalCameraId}) combo is not
+     *                                  part of this {@code MultiResolutionImageReader}.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP)
+    public @NonNull Surface getSurface(@NonNull Size configuredSize,
+            @NonNull String physicalCameraId) {
+        checkNotNull(configuredSize, "configuredSize must not be null");
+        checkNotNull(physicalCameraId, "physicalCameraId must not be null");
+
+        for (int i = 0; i < mStreamInfo.length; i++) {
+            if (mStreamInfo[i].getWidth() == configuredSize.getWidth()
+                    && mStreamInfo[i].getHeight() == configuredSize.getHeight()
+                    && physicalCameraId.equals(mStreamInfo[i].getPhysicalCameraId())) {
+                return mReaders[i].getSurface();
+            }
+        }
+        throw new IllegalArgumentException("configuredSize and physicalCameraId don't match with "
+                + "this MultiResolutionImageReader");
+    }
+
+    /**
      * Get the surface that is used as a target for {@link CaptureRequest}
      *
      * <p>The application must use the surface returned by this function as a target for
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java
index 0f199b1..9e01438 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java
@@ -90,7 +90,6 @@
     @Override
     public boolean isSessionConfigurationSupported(@NonNull SessionConfiguration config)
             throws CameraAccessException {
-        // TODO(b/298033056): restructure the OutputConfiguration API for better usability
         synchronized (mInterfaceLock) {
             if (mCameraManager.isCameraServiceDisabled()) {
                 throw new IllegalArgumentException("No cameras available on device");
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index 8aacd5e..dda52dd 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -19,6 +19,7 @@
 
 import static com.android.internal.util.Preconditions.*;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -27,6 +28,9 @@
 import android.annotation.TestApi;
 import android.graphics.ColorSpace;
 import android.graphics.ImageFormat;
+import android.graphics.ImageFormat.Format;
+import android.hardware.HardwareBuffer;
+import android.hardware.HardwareBuffer.Usage;
 import android.hardware.camera2.CameraCaptureSession;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraDevice;
@@ -43,6 +47,8 @@
 import android.util.Size;
 import android.view.Surface;
 
+import com.android.internal.camera.flags.Flags;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -50,6 +56,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * A class for describing camera output, which contains a {@link Surface} and its specific
@@ -361,6 +368,21 @@
     private final int SURFACE_TYPE_SURFACE_TEXTURE = 1;
 
     /**
+     * The surface is obtained from {@link android.media.MediaRecorder}.
+     */
+    private static final int SURFACE_TYPE_MEDIA_RECORDER = 2;
+
+    /**
+     * The surface is obtained from {@link android.media.MediaCodec}.
+     */
+    private static final int SURFACE_TYPE_MEDIA_CODEC = 3;
+
+    /**
+     * The surface is obtained from {@link android.media.ImageReader}.
+     */
+    private static final int SURFACE_TYPE_IMAGE_READER = 4;
+
+    /**
      * Maximum number of surfaces supported by one {@link OutputConfiguration}.
      *
      * <p>The combined number of surfaces added by the constructor and
@@ -576,6 +598,7 @@
         mMirrorMode = MIRROR_MODE_AUTO;
         mReadoutTimestampEnabled = false;
         mIsReadoutSensorTimestampBase = false;
+        mUsage = 0;
     }
 
     /**
@@ -592,13 +615,7 @@
             @NonNull MultiResolutionImageReader multiResolutionImageReader)  {
         checkNotNull(multiResolutionImageReader, "Multi-resolution image reader must not be null");
 
-        int groupId = MULTI_RESOLUTION_GROUP_ID_COUNTER;
-        MULTI_RESOLUTION_GROUP_ID_COUNTER++;
-        // Skip in case the group id counter overflows to -1, the invalid value.
-        if (MULTI_RESOLUTION_GROUP_ID_COUNTER == -1) {
-            MULTI_RESOLUTION_GROUP_ID_COUNTER++;
-        }
-
+        int groupId = getAndIncreaseMultiResolutionGroupId();
         ImageReader[] imageReaders = multiResolutionImageReader.getReaders();
         ArrayList<OutputConfiguration> configs = new ArrayList<OutputConfiguration>();
         for (int i = 0; i < imageReaders.length; i++) {
@@ -620,6 +637,115 @@
     }
 
     /**
+     * Create a list of {@link OutputConfiguration} instances for a
+     * {@link android.hardware.camera2.params.MultiResolutionImageReader}.
+     *
+     * <p>This method can be used to create query OutputConfigurations for a
+     * MultiResolutionImageReader that can be included in a SessionConfiguration passed into
+     * {@link CameraDeviceSetup#isSessionConfigurationSupported} before opening and setting up
+     * a camera device in full, at which point {@link #setSurfacesForMultiResolutionOutput}
+     * can be used to link to the actual MultiResolutionImageReader.</p>
+     *
+     * <p>This constructor takes same arguments used to create a {@link
+     * MultiResolutionImageReader}: a collection of {@link MultiResolutionStreamInfo}
+     * objects and the format.</p>
+     *
+     * @param streams The group of multi-resolution stream info objects, which are used to create a
+     *                multi-resolution image reader containing a number of ImageReaders.
+     * @param format The format of the MultiResolutionImageReader. This must be one of the {@link
+     *               android.graphics.ImageFormat} or {@link android.graphics.PixelFormat} constants
+     *               supported by the camera device. Note that not all formats are supported, like
+     *               {@link ImageFormat.NV21}. The supported multi-resolution reader format can be
+     *               queried by {@link MultiResolutionStreamConfigurationMap#getOutputFormats}.
+     *
+     * @return The list of {@link OutputConfiguration} objects for a MultiResolutionImageReader.
+     *
+     * @throws IllegaArgumentException If the {@code streams} is null or doesn't contain
+     *                                 at least 2 items, or if {@code format} isn't a valid camera
+     *                                 format.
+     *
+     * @see MultiResolutionImageReader
+     * @see MultiResolutionStreamInfo
+     */
+    @FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP)
+    public static @NonNull List<OutputConfiguration> createInstancesForMultiResolutionOutput(
+            @NonNull Collection<MultiResolutionStreamInfo> streams,
+            @Format int format)  {
+        if (streams == null || streams.size() <= 1) {
+            throw new IllegalArgumentException(
+                "The streams list must contain at least 2 entries");
+        }
+        if (format == ImageFormat.NV21) {
+            throw new IllegalArgumentException(
+                    "NV21 format is not supported");
+        }
+
+        int groupId = getAndIncreaseMultiResolutionGroupId();
+        ArrayList<OutputConfiguration> configs = new ArrayList<OutputConfiguration>();
+        for (MultiResolutionStreamInfo stream : streams) {
+            Size surfaceSize = new Size(stream.getWidth(), stream.getHeight());
+            OutputConfiguration config = new OutputConfiguration(
+                    groupId, format, surfaceSize);
+            config.setPhysicalCameraId(stream.getPhysicalCameraId());
+            config.setMultiResolutionOutput();
+            configs.add(config);
+
+            // No need to call addSensorPixelModeUsed for ultra high resolution sensor camera,
+            // because regular and max resolution output configurations are used for DEFAULT mode
+            // and MAX_RESOLUTION mode respectively by default.
+        }
+
+        return configs;
+    }
+
+    /**
+     * Set the OutputConfiguration surfaces corresponding to the {@link MultiResolutionImageReader}.
+     *
+     * <p>This function should be used together with {@link
+     * #createInstancesForMultiResolutionOutput}. The application calls {@link
+     * #createInstancesForMultiResolutionOutput} first to create a list of
+     * OutputConfiguration objects without the actual MultiResolutionImageReader.
+     * Once the MultiResolutionImageReader is created later during full camera setup, the
+     * application then calls this function to assign the surfaces to the OutputConfiguration
+     * instances.</p>
+     *
+     * @param outputConfigurations The OutputConfiguration objects created by {@link
+     *                             #createInstancesFromMultiResolutionOutput}
+     * @param multiResolutionImageReader The MultiResolutionImageReader object created from the same
+     *                                   MultiResolutionStreamInfo parameters as
+     *                                   {@code outputConfigurations}.
+     * @throws IllegalArgumentException If {@code outputConfigurations} or {@code
+     *                                  multiResolutionImageReader} is {@code null}, the {@code
+     *                                  outputConfigurations} and {@code multiResolutionImageReader}
+     *                                  sizes don't match, or if the
+     *                                  {@code multiResolutionImageReader}'s surfaces don't match
+     *                                  with the {@code outputConfigurations}.
+     * @throws IllegalStateException If {@code outputConfigurations} already contains valid output
+     *                               surfaces.
+     */
+    @FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP)
+    public static void setSurfacesForMultiResolutionOutput(
+            @NonNull Collection<OutputConfiguration> outputConfigurations,
+            @NonNull MultiResolutionImageReader multiResolutionImageReader) {
+        checkNotNull(outputConfigurations, "outputConfigurations must not be null");
+        checkNotNull(multiResolutionImageReader, "multiResolutionImageReader must not be null");
+        if (outputConfigurations.size() != multiResolutionImageReader.getReaders().length) {
+            throw new IllegalArgumentException(
+                    "outputConfigurations and multiResolutionImageReader sizes must match");
+        }
+
+        for (OutputConfiguration config : outputConfigurations) {
+            String physicalCameraId = config.getPhysicalCameraId();
+            if (physicalCameraId == null) {
+                physicalCameraId = "";
+            }
+            Surface surface = multiResolutionImageReader.getSurface(config.getConfiguredSize(),
+                    physicalCameraId);
+            config.addSurface(surface);
+        }
+    }
+
+    /**
      * Create a new {@link OutputConfiguration} instance, with desired Surface size and Surface
      * source class.
      * <p>
@@ -628,30 +754,64 @@
      * with a deferred Surface. The application can use this output configuration to create a
      * session.
      * </p>
-     * <p>
-     * However, the actual output Surface must be set via {@link #addSurface} and the deferred
-     * Surface configuration must be finalized via {@link
-     * CameraCaptureSession#finalizeOutputConfigurations} before submitting a request with this
-     * Surface target. The deferred Surface can only be obtained either from {@link
-     * android.view.SurfaceView} by calling {@link android.view.SurfaceHolder#getSurface}, or from
+     *
+     * <p>Starting from {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM Android V},
+     * the deferred Surface can be obtained: (1) from {@link android.view.SurfaceView}
+     * by calling {@link android.view.SurfaceHolder#getSurface}, (2) from
      * {@link android.graphics.SurfaceTexture} via
-     * {@link android.view.Surface#Surface(android.graphics.SurfaceTexture)}).
-     * </p>
+     * {@link android.view.Surface#Surface(android.graphics.SurfaceTexture)}, (3) from {@link
+     * android.media.MediaRecorder} via {@link android.media.MediaRecorder.getSurface} or {@link
+     * android.media.MediaCodec#createPersistentInputSurface}, or (4) from {@link
+     * android.media.MediaCodce} via {@link android.media.MediaCodec#createInputSurface} or {@link
+     * android.media.MediaCodec#createPersistentInputSource}.</p>
+     *
+     * <ul>
+     * <li>Surfaces for {@link android.view.SurfaceView} and {@link android.graphics.SurfaceTexture}
+     * can be deferred until after {@link CameraDevice#createCaptureSession}. In that case, the
+     * output Surface must be set via {@link #addSurface}, and the Surface configuration must be
+     * finalized via {@link CameraCaptureSession#finalizeOutputConfiguration} before submitting
+     * a request with the Surface target.</li>
+     * <li>For all other target types, the output Surface must be set by {@link #addSurface},
+     * and {@link CameraCaptureSession#finalizeOutputConfiguration} is not needed because the
+     * OutputConfiguration used to create the session will contain the actual Surface.</li>
+     * </ul>
+     *
+     * <p>Before {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM Android V}, only {@link
+     * android.view.SurfaceView} and {@link android.graphics.SurfaceTexture} are supported. Both
+     * kind of outputs can be deferred until after {@link
+     * CameraDevice#createCaptureSessionByOutputConfiguration}.</p>
+     *
+     * <p>An OutputConfiguration object created by this constructor can be used for {@link
+     * CameraDeviceSetup.isSessionConfigurationSupported} and {@link
+     * CameraDeviceSetup.getSessionCharacteristics} without having called {@link #addSurface}.</p>
      *
      * @param surfaceSize Size for the deferred surface.
      * @param klass a non-{@code null} {@link Class} object reference that indicates the source of
-     *            this surface. Only {@link android.view.SurfaceHolder SurfaceHolder.class} and
-     *            {@link android.graphics.SurfaceTexture SurfaceTexture.class} are supported.
+     *            this surface. Only {@link android.view.SurfaceHolder SurfaceHolder.class},
+     *            {@link android.graphics.SurfaceTexture SurfaceTexture.class}, {@link
+     *            android.media.MediaRecorder MediaRecorder.class}, and
+     *            {@link android.media.MediaCodec MediaCodec.class} are supported.
+     *            Before {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM Android V}, only
+     *            {@link android.view.SurfaceHolder SurfaceHolder.class} and {@link
+     *            android.graphics.SurfaceTexture SurfaceTexture.class} are supported.
      * @throws IllegalArgumentException if the Surface source class is not supported, or Surface
      *         size is zero.
      */
     public <T> OutputConfiguration(@NonNull Size surfaceSize, @NonNull Class<T> klass) {
-        checkNotNull(klass, "surfaceSize must not be null");
+        checkNotNull(surfaceSize, "surfaceSize must not be null");
         checkNotNull(klass, "klass must not be null");
         if (klass == android.view.SurfaceHolder.class) {
             mSurfaceType = SURFACE_TYPE_SURFACE_VIEW;
+            mIsDeferredConfig = true;
         } else if (klass == android.graphics.SurfaceTexture.class) {
             mSurfaceType = SURFACE_TYPE_SURFACE_TEXTURE;
+            mIsDeferredConfig = true;
+        } else if (klass == android.media.MediaRecorder.class) {
+            mSurfaceType = SURFACE_TYPE_MEDIA_RECORDER;
+            mIsDeferredConfig = false;
+        } else if (klass == android.media.MediaCodec.class) {
+            mSurfaceType = SURFACE_TYPE_MEDIA_CODEC;
+            mIsDeferredConfig = false;
         } else {
             mSurfaceType = SURFACE_TYPE_UNKNOWN;
             throw new IllegalArgumentException("Unknown surface source class type");
@@ -668,7 +828,6 @@
         mConfiguredFormat = StreamConfigurationMap.imageFormatToInternal(ImageFormat.PRIVATE);
         mConfiguredDataspace = StreamConfigurationMap.imageFormatToDataspace(ImageFormat.PRIVATE);
         mConfiguredGenerationId = 0;
-        mIsDeferredConfig = true;
         mIsShared = false;
         mPhysicalCameraId = null;
         mIsMultiResolution = false;
@@ -678,6 +837,131 @@
         mStreamUseCase = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT;
         mReadoutTimestampEnabled = false;
         mIsReadoutSensorTimestampBase = false;
+        mUsage = 0;
+    }
+
+    /**
+     * Create a new {@link OutputConfiguration} instance for an {@link ImageReader} for a given
+     * format and size.
+     *
+     * <p>This constructor creates an OutputConfiguration for an ImageReader without providing
+     * the actual output Surface. The actual output Surface must be set via {@link #addSurface}
+     * before creating the capture session.</p>
+     *
+     * <p>An OutputConfiguration object created by this constructor can be used for {@link
+     * CameraDeviceSetup.isSessionConfigurationSupported} and {@link
+     * CameraDeviceSetup.getSessionCharacteristics} without having called {@link #addSurface}.</p>
+     *
+     * @param format The format of the ImageReader output. This must be one of the
+     *               {@link android.graphics.ImageFormat} or {@link android.graphics.PixelFormat}
+     *               constants. Note that not all formats are supported by the camera device.
+     * @param surfaceSize Size for the ImageReader surface.
+     * @throws IllegalArgumentException if the Surface size is null or zero.
+     */
+    @FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP)
+    public OutputConfiguration(@Format int format, @NonNull Size surfaceSize) {
+        this(format, surfaceSize,
+                format == ImageFormat.PRIVATE ? 0 : HardwareBuffer.USAGE_CPU_READ_OFTEN);
+    }
+
+    /**
+     * Create a new {@link OutputConfiguration} instance for an {@link ImageReader} for a given
+     * surfaceGroupId, format, and size.
+     *
+     * <p>This constructor creates an OutputConfiguration for an ImageReader without providing
+     * the actual output Surface. The actual output Surface must be set via {@link #addSurface}
+     * before creating the capture session.</p>
+     *
+     * <p>An OutputConfiguration object created by this constructor can be used for {@link
+     * CameraDeviceSetup.isSessionConfigurationSupported} and {@link
+     * CameraDeviceSetup.getSessionCharacteristics} without having called {@link #addSurface}.</p>
+     *
+     * @param surfaceGroupId A group ID for this output, used for sharing memory between multiple
+     *                       outputs.
+     * @param format The format of the ImageReader output. This must be one of the
+     *               {@link android.graphics.ImageFormat} or {@link android.graphics.PixelFormat}
+     *               constants. Note that not all formats are supported by the camera device.
+     * @param surfaceSize Size for the ImageReader surface.
+     * @throws IllegalArgumentException if the Surface size is null or zero.
+     */
+    @FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP)
+    public OutputConfiguration(int surfaceGroupId, @Format int format, @NonNull Size surfaceSize) {
+        this(surfaceGroupId, format, surfaceSize,
+                format == ImageFormat.PRIVATE ? 0 : HardwareBuffer.USAGE_CPU_READ_OFTEN);
+    }
+
+    /**
+     * Create a new {@link OutputConfiguration} instance for an {@link ImageReader} for a given
+     * format, size, and usage flags.
+     *
+     * <p>This constructor creates an OutputConfiguration for an ImageReader without providing
+     * the actual output Surface. The actual output Surface must be set via {@link #addSurface}
+     * before creating the capture session.</p>
+     *
+     * <p>An OutputConfiguration object created by this constructor can be used for {@link
+     * CameraDeviceSetup.isSessionConfigurationSupported} and {@link
+     * CameraDeviceSetup.getSessionCharacteristics} without having called {@link #addSurface}.</p>
+     *
+     * @param format The format of the ImageReader output. This must be one of the
+     *               {@link android.graphics.ImageFormat} or {@link android.graphics.PixelFormat}
+     *               constants. Note that not all formats are supported by the camera device.
+     * @param surfaceSize Size for the ImageReader surface.
+     * @param usage The usage flags of the ImageReader output surface.
+     * @throws IllegalArgumentException if the Surface size is null or zero.
+     */
+    @FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP)
+    public OutputConfiguration(@Format int format, @NonNull Size surfaceSize, @Usage long usage) {
+        this(SURFACE_GROUP_ID_NONE, format, surfaceSize, usage);
+    }
+
+    /**
+     * Create a new {@link OutputConfiguration} instance for an {@link ImageReader} for a given
+     * surface group id, format, size, and usage flags.
+     *
+     * <p>This constructor creates an OutputConfiguration for an ImageReader without providing
+     * the actual output Surface. The actual output Surface must be set via {@link #addSurface}
+     * before creating the capture session.</p>
+     *
+     * <p>An OutputConfiguration object created by this constructor can be used for {@link
+     * CameraDeviceSetup.isSessionConfigurationSupported} and {@link
+     * CameraDeviceSetup.getSessionCharacteristics} without having called {@link #addSurface}.</p>
+     *
+     * @param surfaceGroupId A group ID for this output, used for sharing memory between multiple
+     *                       outputs.
+     * @param format The format of the ImageReader output. This must be one of the
+     *               {@link android.graphics.ImageFormat} or {@link android.graphics.PixelFormat}
+     *               constants. Note that not all formats are supported by the camera device.
+     * @param surfaceSize Size for the ImageReader surface.
+     * @param usage The usage flags of the ImageReader output surface.
+     * @throws IllegalArgumentException if the Surface size is null or zero.
+     */
+    @FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP)
+    public OutputConfiguration(int surfaceGroupId, @Format int format,
+            @NonNull Size surfaceSize, @Usage long usage) {
+        checkNotNull(surfaceSize, "surfaceSize must not be null");
+        if (surfaceSize.getWidth() == 0 || surfaceSize.getHeight() == 0) {
+            throw new IllegalArgumentException("Surface size needs to be non-zero");
+        }
+
+        mSurfaceType = SURFACE_TYPE_IMAGE_READER;
+        mSurfaceGroupId = surfaceGroupId;
+        mSurfaces = new ArrayList<Surface>();
+        mRotation = ROTATION_0;
+        mConfiguredSize = surfaceSize;
+        mConfiguredFormat = StreamConfigurationMap.imageFormatToInternal(format);
+        mConfiguredDataspace = StreamConfigurationMap.imageFormatToDataspace(format);
+        mConfiguredGenerationId = 0;
+        mIsDeferredConfig = false;
+        mIsShared = false;
+        mPhysicalCameraId = null;
+        mIsMultiResolution = false;
+        mSensorPixelModesUsed = new ArrayList<Integer>();
+        mDynamicRangeProfile = DynamicRangeProfiles.STANDARD;
+        mColorSpace = ColorSpaceProfiles.UNSPECIFIED;
+        mStreamUseCase = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT;
+        mReadoutTimestampEnabled = false;
+        mIsReadoutSensorTimestampBase = false;
+        mUsage = usage;
     }
 
     /**
@@ -852,7 +1136,8 @@
     /**
      * Check if this configuration has deferred configuration.
      *
-     * <p>This will return true if the output configuration was constructed with surface deferred by
+     * <p>This will return true if the output configuration was constructed with {@link
+     * android.view.SurfaceView} or {@link android.graphics.SurfaceTexture} deferred by
      * {@link OutputConfiguration#OutputConfiguration(Size, Class)}. It will return true even after
      * the deferred surface is added later by {@link OutputConfiguration#addSurface}.</p>
      *
@@ -872,13 +1157,28 @@
      * {@link CameraCaptureSession#finalizeOutputConfigurations}. It is possible to call this method
      * after the output configurations have been finalized only in cases of enabled surface sharing
      * see {@link #enableSurfaceSharing}. The modified output configuration must be updated with
-     * {@link CameraCaptureSession#updateOutputConfiguration}.</p>
+     * {@link CameraCaptureSession#updateOutputConfiguration}. If this function is called before
+     * session creation, {@link CameraCaptureSession#finalizeOutputConfigurations} doesn't need to
+     * be called.</p>
      *
-     * <p> If the OutputConfiguration was constructed with a deferred surface by {@link
-     * OutputConfiguration#OutputConfiguration(Size, Class)}, the added surface must be obtained
-     * from {@link android.view.SurfaceView} by calling {@link android.view.SurfaceHolder#getSurface},
-     * or from {@link android.graphics.SurfaceTexture} via
-     * {@link android.view.Surface#Surface(android.graphics.SurfaceTexture)}).</p>
+     * <p> If the OutputConfiguration was constructed by {@link
+     * OutputConfiguration#OutputConfiguration(Size, Class)}, the added surface must be obtained:
+     * <ul>
+     * <li>from {@link android.view.SurfaceView} by calling
+     * {@link android.view.SurfaceHolder#getSurface}</li>
+     * <li>from {@link android.graphics.SurfaceTexture} by calling
+     * {@link android.view.Surface#Surface(android.graphics.SurfaceTexture)}</li>
+     * <li>from {@link android.media.MediaRecorder} by calling
+     * {@link android.media.MediaRecorder#getSurface} or {@link
+     * android.media.MediaCodec#createPersistentInputSurface}</li>
+     * <li>from {@link android.media.MediaCodce} by calling
+     * {@link android.media.MediaCodec#createInputSurface} or {@link
+     * android.media.MediaCodec#createPersistentInputSource}</li>
+     * </ul>
+     *
+     * <p> If the OutputConfiguration was constructed by {@link #OutputConfiguration(int, Size)}
+     * or its variants, the added surface must be obtained from {@link android.media.ImageReader}
+     * by calling {@link android.media.ImageReader#getSurface}.</p>
      *
      * <p> If the OutputConfiguration was constructed by other constructors, the added
      * surface must be compatible with the existing surface. See {@link #enableSurfaceSharing} for
@@ -934,9 +1234,13 @@
      *
      * <p> Surfaces added via calls to {@link #addSurface} can also be removed from the
      *  OutputConfiguration. The only notable exception is the surface associated with
-     *  the OutputConfiguration see {@link #getSurface} which was passed as part of the constructor
-     *  or was added first in the deferred case
-     *  {@link OutputConfiguration#OutputConfiguration(Size, Class)}.</p>
+     *  the OutputConfiguration (see {@link #getSurface}) which was passed as part of the
+     *  constructor or was added first in the case of
+     *  {@link OutputConfiguration#OutputConfiguration(Size, Class)}, {@link
+     *  OutputConfiguration#OutputConfiguration(int, Size)}, {@link
+     *  OutputConfiguration#OutputConfiguration(int, Size, long)}, {@link
+     *  OutputConfiguration#OutputConfiguration(int, int, Size)}, {@link
+     *  OutputConfiguration#OutputConfiguration(int, int, Size, long)}.</p>
      *
      * @param surface The surface to be removed.
      *
@@ -945,6 +1249,7 @@
      *                                  with {@link #addSurface}.
      */
     public void removeSurface(@NonNull Surface surface) {
+        checkNotNull(surface, "Surface must not be null");
         if (getSurface() == surface) {
             throw new IllegalArgumentException(
                     "Cannot remove surface associated with this output configuration");
@@ -1175,6 +1480,7 @@
         this.mTimestampBase = other.mTimestampBase;
         this.mMirrorMode = other.mMirrorMode;
         this.mReadoutTimestampEnabled = other.mReadoutTimestampEnabled;
+        this.mUsage = other.mUsage;
     }
 
     /**
@@ -1203,6 +1509,9 @@
         int timestampBase = source.readInt();
         int mirrorMode = source.readInt();
         boolean readoutTimestampEnabled = source.readInt() == 1;
+        int format = source.readInt();
+        int dataSpace = source.readInt();
+        long usage = source.readLong();
 
         mSurfaceGroupId = surfaceSetId;
         mRotation = rotation;
@@ -1211,6 +1520,7 @@
         mIsDeferredConfig = isDeferred;
         mIsShared = isShared;
         mSurfaces = surfaces;
+        mUsage = 0;
         if (mSurfaces.size() > 0) {
             mSurfaceType = SURFACE_TYPE_UNKNOWN;
             mConfiguredFormat = SurfaceUtils.getSurfaceFormat(mSurfaces.get(0));
@@ -1218,9 +1528,16 @@
             mConfiguredGenerationId = mSurfaces.get(0).getGenerationId();
         } else {
             mSurfaceType = surfaceType;
-            mConfiguredFormat = StreamConfigurationMap.imageFormatToInternal(ImageFormat.PRIVATE);
-            mConfiguredDataspace =
-                    StreamConfigurationMap.imageFormatToDataspace(ImageFormat.PRIVATE);
+            if (mSurfaceType != SURFACE_TYPE_IMAGE_READER) {
+                mConfiguredFormat = StreamConfigurationMap.imageFormatToInternal(
+                        ImageFormat.PRIVATE);
+                mConfiguredDataspace =
+                        StreamConfigurationMap.imageFormatToDataspace(ImageFormat.PRIVATE);
+            } else {
+                mConfiguredFormat = format;
+                mConfiguredDataspace = dataSpace;
+                mUsage = usage;
+            }
             mConfiguredGenerationId = 0;
         }
         mPhysicalCameraId = physicalCameraId;
@@ -1293,6 +1610,31 @@
         return mSurfaceGroupId;
     }
 
+    /**
+     * Get the configured size associated with this {@link OutputConfiguration}.
+     *
+     * @return The configured size associated with this {@link OutputConfiguration}.
+     *
+     * @hide
+     */
+    public Size getConfiguredSize() {
+        return mConfiguredSize;
+    }
+
+    /**
+     * Get the physical camera ID associated with this {@link OutputConfiguration}.
+     *
+     * <p>If this OutputConfiguration isn't targeting a physical camera of a logical
+     * multi-camera, this function returns {@code null}.</p>
+     *
+     * @return The physical camera Id associated with this {@link OutputConfiguration}.
+     *
+     * @hide
+     */
+    public @Nullable String getPhysicalCameraId() {
+        return mPhysicalCameraId;
+    }
+
     public static final @android.annotation.NonNull Parcelable.Creator<OutputConfiguration> CREATOR =
             new Parcelable.Creator<OutputConfiguration>() {
         @Override
@@ -1353,6 +1695,9 @@
         dest.writeInt(mTimestampBase);
         dest.writeInt(mMirrorMode);
         dest.writeInt(mReadoutTimestampEnabled ? 1 : 0);
+        dest.writeInt(mConfiguredFormat);
+        dest.writeInt(mConfiguredDataspace);
+        dest.writeLong(mUsage);
     }
 
     /**
@@ -1372,22 +1717,24 @@
             return true;
         } else if (obj instanceof OutputConfiguration) {
             final OutputConfiguration other = (OutputConfiguration) obj;
-            if (mRotation != other.mRotation ||
-                    !mConfiguredSize.equals(other.mConfiguredSize) ||
-                    mConfiguredFormat != other.mConfiguredFormat ||
-                    mSurfaceGroupId != other.mSurfaceGroupId ||
-                    mSurfaceType != other.mSurfaceType ||
-                    mIsDeferredConfig != other.mIsDeferredConfig ||
-                    mIsShared != other.mIsShared ||
-                    mConfiguredDataspace != other.mConfiguredDataspace ||
-                    mConfiguredGenerationId != other.mConfiguredGenerationId ||
-                    !Objects.equals(mPhysicalCameraId, other.mPhysicalCameraId) ||
-                    mIsMultiResolution != other.mIsMultiResolution ||
-                    mStreamUseCase != other.mStreamUseCase ||
-                    mTimestampBase != other.mTimestampBase ||
-                    mMirrorMode != other.mMirrorMode ||
-                    mReadoutTimestampEnabled != other.mReadoutTimestampEnabled)
+            if (mRotation != other.mRotation
+                    || !mConfiguredSize.equals(other.mConfiguredSize)
+                    || mConfiguredFormat != other.mConfiguredFormat
+                    || mSurfaceGroupId != other.mSurfaceGroupId
+                    || mSurfaceType != other.mSurfaceType
+                    || mIsDeferredConfig != other.mIsDeferredConfig
+                    || mIsShared != other.mIsShared
+                    || mConfiguredDataspace != other.mConfiguredDataspace
+                    || mConfiguredGenerationId != other.mConfiguredGenerationId
+                    || !Objects.equals(mPhysicalCameraId, other.mPhysicalCameraId)
+                    || mIsMultiResolution != other.mIsMultiResolution
+                    || mStreamUseCase != other.mStreamUseCase
+                    || mTimestampBase != other.mTimestampBase
+                    || mMirrorMode != other.mMirrorMode
+                    || mReadoutTimestampEnabled != other.mReadoutTimestampEnabled
+                    || mUsage != other.mUsage) {
                 return false;
+            }
             if (mSensorPixelModesUsed.size() != other.mSensorPixelModesUsed.size()) {
                 return false;
             }
@@ -1416,6 +1763,16 @@
     }
 
     /**
+     * Get and increase the next MultiResolution group id.
+     *
+     * If the ID reaches -1, skip it.
+     */
+    private static int getAndIncreaseMultiResolutionGroupId() {
+        return sNextMultiResolutionGroupId.getAndUpdate(i ->
+                i + 1 == SURFACE_GROUP_ID_NONE ? i + 2 : i + 1);
+    }
+
+    /**
      * {@inheritDoc}
      */
     @Override
@@ -1430,7 +1787,8 @@
                     mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode(),
                     mIsMultiResolution ? 1 : 0, mSensorPixelModesUsed.hashCode(),
                     mDynamicRangeProfile, mColorSpace, mStreamUseCase,
-                    mTimestampBase, mMirrorMode, mReadoutTimestampEnabled ? 1 : 0);
+                    mTimestampBase, mMirrorMode, mReadoutTimestampEnabled ? 1 : 0,
+                    Long.hashCode(mUsage));
         }
 
         return HashCodeHelpers.hashCode(
@@ -1440,14 +1798,14 @@
                 mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode(),
                 mIsMultiResolution ? 1 : 0, mSensorPixelModesUsed.hashCode(),
                 mDynamicRangeProfile, mColorSpace, mStreamUseCase, mTimestampBase,
-                mMirrorMode, mReadoutTimestampEnabled ? 1 : 0);
+                mMirrorMode, mReadoutTimestampEnabled ? 1 : 0, Long.hashCode(mUsage));
     }
 
     private static final String TAG = "OutputConfiguration";
 
     // A surfaceGroupId counter used for MultiResolutionImageReader. Its value is
     // incremented every time {@link createInstancesForMultiResolutionOutput} is called.
-    private static int MULTI_RESOLUTION_GROUP_ID_COUNTER = 0;
+    private static AtomicInteger sNextMultiResolutionGroupId = new AtomicInteger(0);
 
     private ArrayList<Surface> mSurfaces;
     private final int mRotation;
@@ -1486,4 +1844,6 @@
     private boolean mReadoutTimestampEnabled;
     // Whether the timestamp base is set to READOUT_SENSOR
     private boolean mIsReadoutSensorTimestampBase;
+    // The usage flags. Only set for instances created for ImageReader without specifying surface.
+    private long mUsage;
 }
diff --git a/core/java/android/hardware/camera2/params/SessionConfiguration.java b/core/java/android/hardware/camera2/params/SessionConfiguration.java
index 991f545..b0f354f 100644
--- a/core/java/android/hardware/camera2/params/SessionConfiguration.java
+++ b/core/java/android/hardware/camera2/params/SessionConfiguration.java
@@ -20,6 +20,7 @@
 import static com.android.internal.util.Preconditions.*;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -28,6 +29,7 @@
 import android.hardware.camera2.CameraCaptureSession;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraDevice.CameraDeviceSetup;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.params.InputConfiguration;
@@ -125,6 +127,34 @@
     }
 
     /**
+     * Create a new {@link SessionConfiguration} with sessionType and output configurations.
+     *
+     * <p>The SessionConfiguration objects created by this constructor can be used by
+     * {@link CameraDeviceSetup.isSessionConfigurationSupported} and {@link
+     * CameraDeviceSetup.getSessionCharacteristics} to query a camera device's feature
+     * combination support and session specific characteristics. For the SessionConfiguration
+     * object to be used to create a capture session, {@link #setCallback} must be called to
+     * specify the state callback function, and any incomplete OutputConfigurations must be
+     * completed via {@link OutputConfiguration#addSurface} or
+     * {@link OutputConfiguration#setSurfacesForMultiResolutionOutput} as appropriate.</p>
+     *
+     * @param sessionType The session type.
+     * @param outputs A list of output configurations for the capture session.
+     *
+     * @see #SESSION_REGULAR
+     * @see #SESSION_HIGH_SPEED
+     * @see CameraDevice#createCaptureSession(SessionConfiguration)
+     * @see CameraDeviceSetup#isSessionConfigurationSupported
+     * @see CameraDeviceSetup#getSessionCharacteristics
+     */
+    @FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP)
+    public SessionConfiguration(@SessionMode int sessionType,
+            @NonNull List<OutputConfiguration> outputs) {
+        mSessionType = sessionType;
+        mOutputConfigurations = Collections.unmodifiableList(new ArrayList<>(outputs));
+    }
+
+    /**
      * Create a SessionConfiguration from Parcel.
      * No support for parcelable 'mStateCallback' and 'mExecutor' yet.
      */
@@ -376,4 +406,23 @@
             return null;
         }
     }
+
+    /**
+     * Set the state callback and executor.
+     *
+     * <p>This function must be called for the SessionConfiguration object created via {@link
+     * #SessionConfiguration(int, List) SessionConfiguration(int, List&lt;OutputConfiguration&gt;)}
+     * before it's used to create a capture session.</p>
+     *
+     * @param executor The executor which should be used to invoke the callback. In general it is
+     *                 recommended that camera operations are not done on the main (UI) thread.
+     * @param cb A state callback interface implementation.
+     */
+    @FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP)
+    public void setCallback(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull CameraCaptureSession.StateCallback cb) {
+        mStateCallback = cb;
+        mExecutor = executor;
+    }
 }
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index b85d686..b067095 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -1509,6 +1509,8 @@
                 return HAL_DATASPACE_HEIF;
             case ImageFormat.JPEG_R:
                 return HAL_DATASPACE_JPEG_R;
+            case ImageFormat.YUV_420_888:
+                return HAL_DATASPACE_JFIF;
             default:
                 return HAL_DATASPACE_UNKNOWN;
         }
@@ -2025,6 +2027,10 @@
      * @hide
      */
     public static final int HAL_DATASPACE_JPEG_R = 0x1005;
+    /**
+     * @hide
+     */
+    public static final int HAL_DATASPACE_JFIF = 0x8C20000;
     private static final long DURATION_20FPS_NS = 50000000L;
     /**
      * @see #getDurations(int, int)
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..84619a0 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1024,7 +1024,11 @@
     /**
      * Specifies if a user is disallowed from creating a private profile.
      * <p>The default value for an unmanaged user is <code>false</code>.
-     * For users with a device owner set, the default is <code>true</code>.
+     * For users with a device owner set, the default value is <code>true</code> and the
+     * device owner currently cannot change it to <code>false</code>.
+     * On organization-owned managed profile devices, the default value is <code>false</code> but
+     * the profile owner can change it to <code>true</code> via the parent profile to block creating
+     * of private profiles on the personal user.
      *
      * <p>Holders of the permission
      * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_PROFILES}
@@ -1034,9 +1038,10 @@
      * <p>Type: Boolean
      * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
      * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#getParentProfileInstance(ComponentName)
      * @see #getUserRestrictions()
-     * @hide
      */
+    @FlaggedApi(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE)
     public static final String DISALLOW_ADD_PRIVATE_PROFILE = "no_add_private_profile";
 
     /**
@@ -3189,6 +3194,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/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 2d657c2..4060b24 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -18,7 +18,9 @@
 
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IdRes;
+import android.annotation.IntDef;
 import android.annotation.LayoutRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -47,6 +49,7 @@
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.service.controls.flags.Flags;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.MathUtils;
@@ -76,6 +79,8 @@
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.function.Consumer;
 
 /**
@@ -217,6 +222,33 @@
      */
     public static final boolean DEFAULT_SHOW_COMPLICATIONS = false;
 
+    /**
+     * The default value for dream category
+     * @hide
+     */
+    public static final int DREAM_CATEGORY_DEFAULT = 0;
+
+    /**
+     * Dream category for Low Light Dream
+     * @hide
+     */
+    public static final int DREAM_CATEGORY_LOW_LIGHT = 1 << 0;
+
+    /**
+     * Dream category for Home Panel Dream
+     * @hide
+     */
+    public static final int DREAM_CATEGORY_HOME_PANEL = 1 << 1;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = {"DREAM_CATEGORY"}, value = {
+        DREAM_CATEGORY_DEFAULT,
+        DREAM_CATEGORY_LOW_LIGHT,
+        DREAM_CATEGORY_HOME_PANEL
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface DreamCategory {}
+
     private final IDreamManager mDreamManager;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private IBinder mDreamToken;
@@ -1122,12 +1154,15 @@
         try (TypedArray rawMetadata = readMetadata(pm, serviceInfo)) {
             if (rawMetadata == null) return null;
             return new DreamMetadata(
-                    convertToComponentName(rawMetadata.getString(
+                    convertToComponentName(
+                    rawMetadata.getString(
                             com.android.internal.R.styleable.Dream_settingsActivity), serviceInfo),
                     rawMetadata.getDrawable(
                             com.android.internal.R.styleable.Dream_previewImage),
                     rawMetadata.getBoolean(R.styleable.Dream_showClockAndComplications,
-                            DEFAULT_SHOW_COMPLICATIONS));
+                            DEFAULT_SHOW_COMPLICATIONS),
+                    rawMetadata.getInt(R.styleable.Dream_dreamCategory, DREAM_CATEGORY_DEFAULT)
+                    );
         }
     }
 
@@ -1543,11 +1578,23 @@
         @NonNull
         public final boolean showComplications;
 
-        DreamMetadata(ComponentName settingsActivity, Drawable previewImage,
-                boolean showComplications) {
+        @NonNull
+        @FlaggedApi(Flags.FLAG_HOME_PANEL_DREAM)
+        public final int dreamCategory;
+
+        DreamMetadata(
+                ComponentName settingsActivity,
+                Drawable previewImage,
+                boolean showComplications,
+                int dreamCategory) {
             this.settingsActivity = settingsActivity;
             this.previewImage = previewImage;
             this.showComplications = showComplications;
+            if (Flags.homePanelDream()) {
+                this.dreamCategory = dreamCategory;
+            } else {
+                this.dreamCategory = DREAM_CATEGORY_DEFAULT;
+            }
         }
     }
 }
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/OnDeviceIntelligenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
index 27e8628..6815440 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
@@ -222,7 +222,10 @@
 
     /**
      * 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.
diff --git a/core/java/android/service/persistentdata/PersistentDataBlockManager.java b/core/java/android/service/persistentdata/PersistentDataBlockManager.java
index 9b9cc19..7a1c75a 100644
--- a/core/java/android/service/persistentdata/PersistentDataBlockManager.java
+++ b/core/java/android/service/persistentdata/PersistentDataBlockManager.java
@@ -19,6 +19,7 @@
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
@@ -100,7 +101,7 @@
      */
     @SystemApi
     @SuppressLint("RequiresPermission")
-    public int write(byte[] data) {
+    public int write(@Nullable byte[] data) {
         try {
             return sService.write(data);
         } catch (RemoteException e) {
@@ -115,7 +116,7 @@
      */
     @SystemApi
     @SuppressLint("RequiresPermission")
-    public byte[] read() {
+    public @Nullable byte[] read() {
         try {
             return sService.read();
         } catch (RemoteException e) {
diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java
index 9db8aa1..e95b216 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -28,6 +28,7 @@
 import android.graphics.Path;
 import android.graphics.RectF;
 import android.graphics.text.LineBreakConfig;
+import android.os.Trace;
 import android.text.style.ParagraphStyle;
 
 /**
@@ -567,49 +568,59 @@
     public static @Nullable Metrics isBoring(@NonNull CharSequence text, @NonNull TextPaint paint,
             @NonNull TextDirectionHeuristic textDir, boolean useFallbackLineSpacing,
             @Nullable Paint.FontMetrics minimumFontMetrics, @Nullable Metrics metrics) {
-        final int textLength = text.length();
-        if (hasAnyInterestingChars(text, textLength)) {
-           return null;  // There are some interesting characters. Not boring.
+        if (TRACE_LAYOUT) {
+            Trace.beginSection("BoringLayout#isBoring");
+            Trace.setCounter("BoringLayout#textLength", text.length());
         }
-        if (textDir != null && textDir.isRtl(text, 0, textLength)) {
-           return null;  // The heuristic considers the whole text RTL. Not boring.
-        }
-        if (text instanceof Spanned) {
-            Spanned sp = (Spanned) text;
-            Object[] styles = sp.getSpans(0, textLength, ParagraphStyle.class);
-            if (styles.length > 0) {
-                return null;  // There are some ParagraphStyle spans. Not boring.
+        try {
+            final int textLength = text.length();
+            if (hasAnyInterestingChars(text, textLength)) {
+                return null;  // There are some interesting characters. Not boring.
+            }
+            if (textDir != null && textDir.isRtl(text, 0, textLength)) {
+                return null;  // The heuristic considers the whole text RTL. Not boring.
+            }
+            if (text instanceof Spanned) {
+                Spanned sp = (Spanned) text;
+                Object[] styles = sp.getSpans(0, textLength, ParagraphStyle.class);
+                if (styles.length > 0) {
+                    return null;  // There are some ParagraphStyle spans. Not boring.
+                }
+            }
+
+            Metrics fm = metrics;
+            if (fm == null) {
+                fm = new Metrics();
+            } else {
+                fm.reset();
+            }
+
+            if (ClientFlags.fixLineHeightForLocale()) {
+                if (minimumFontMetrics != null) {
+                    fm.set(minimumFontMetrics);
+                    // Because the font metrics is provided by public APIs, adjust the top/bottom
+                    // with ascent/descent: top must be smaller than ascent, bottom must be larger
+                    // than descent.
+                    fm.top = Math.min(fm.top, fm.ascent);
+                    fm.bottom = Math.max(fm.bottom, fm.descent);
+                }
+            }
+
+            TextLine line = TextLine.obtain();
+            line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT,
+                    Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null,
+                    0 /* ellipsisStart, 0 since text has not been ellipsized at this point */,
+                    0 /* ellipsisEnd, 0 since text has not been ellipsized at this point */,
+                    useFallbackLineSpacing);
+            fm.width = (int) Math.ceil(line.metrics(fm, fm.mDrawingBounds, false, null));
+            TextLine.recycle(line);
+
+            return fm;
+        } finally {
+            if (TRACE_LAYOUT) {
+                Trace.endSection();
             }
         }
-
-        Metrics fm = metrics;
-        if (fm == null) {
-            fm = new Metrics();
-        } else {
-            fm.reset();
-        }
-
-        if (ClientFlags.fixLineHeightForLocale()) {
-            if (minimumFontMetrics != null) {
-                fm.set(minimumFontMetrics);
-                // Because the font metrics is provided by public APIs, adjust the top/bottom with
-                // ascent/descent: top must be smaller than ascent, bottom must be larger than
-                // descent.
-                fm.top = Math.min(fm.top, fm.ascent);
-                fm.bottom = Math.max(fm.bottom, fm.descent);
-            }
-        }
-
-        TextLine line = TextLine.obtain();
-        line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT,
-                Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null,
-                0 /* ellipsisStart, 0 since text has not been ellipsized at this point */,
-                0 /* ellipsisEnd, 0 since text has not been ellipsized at this point */,
-                useFallbackLineSpacing);
-        fm.width = (int) Math.ceil(line.metrics(fm, fm.mDrawingBounds, false, null));
-        TextLine.recycle(line);
-
-        return fm;
     }
 
     @Override
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index cce4f7b..99ce0ef 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -31,6 +31,7 @@
 import android.graphics.Rect;
 import android.graphics.text.LineBreakConfig;
 import android.os.Build;
+import android.os.Trace;
 import android.text.method.OffsetMapping;
 import android.text.style.ReplacementSpan;
 import android.text.style.UpdateLayout;
@@ -636,207 +637,224 @@
     /** @hide */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public void reflow(CharSequence s, int where, int before, int after) {
-        if (s != mBase)
-            return;
-
-        CharSequence text = mDisplay;
-        int len = text.length();
-
-        // seek back to the start of the paragraph
-
-        int find = TextUtils.lastIndexOf(text, '\n', where - 1);
-        if (find < 0)
-            find = 0;
-        else
-            find = find + 1;
-
-        {
-            int diff = where - find;
-            before += diff;
-            after += diff;
-            where -= diff;
+        if (TRACE_LAYOUT) {
+            Trace.beginSection("DynamicLayout#reflow");
         }
-
-        // seek forward to the end of the paragraph
-
-        int look = TextUtils.indexOf(text, '\n', where + after);
-        if (look < 0)
-            look = len;
-        else
-            look++; // we want the index after the \n
-
-        int change = look - (where + after);
-        before += change;
-        after += change;
-
-        // seek further out to cover anything that is forced to wrap together
-
-        if (text instanceof Spanned) {
-            Spanned sp = (Spanned) text;
-            boolean again;
-
-            do {
-                again = false;
-
-                Object[] force = sp.getSpans(where, where + after,
-                                             WrapTogetherSpan.class);
-
-                for (int i = 0; i < force.length; i++) {
-                    int st = sp.getSpanStart(force[i]);
-                    int en = sp.getSpanEnd(force[i]);
-
-                    if (st < where) {
-                        again = true;
-
-                        int diff = where - st;
-                        before += diff;
-                        after += diff;
-                        where -= diff;
-                    }
-
-                    if (en > where + after) {
-                        again = true;
-
-                        int diff = en - (where + after);
-                        before += diff;
-                        after += diff;
-                    }
-                }
-            } while (again);
-        }
-
-        // find affected region of old layout
-
-        int startline = getLineForOffset(where);
-        int startv = getLineTop(startline);
-
-        int endline = getLineForOffset(where + before);
-        if (where + after == len)
-            endline = getLineCount();
-        int endv = getLineTop(endline);
-        boolean islast = (endline == getLineCount());
-
-        // generate new layout for affected text
-
-        StaticLayout reflowed;
-        StaticLayout.Builder b;
-
-        synchronized (sLock) {
-            reflowed = sStaticLayout;
-            b = sBuilder;
-            sStaticLayout = null;
-            sBuilder = null;
-        }
-
-        if (b == null) {
-            b = StaticLayout.Builder.obtain(text, where, where + after, getPaint(), getWidth());
-        }
-
-        b.setText(text, where, where + after)
-                .setPaint(getPaint())
-                .setWidth(getWidth())
-                .setTextDirection(getTextDirectionHeuristic())
-                .setLineSpacing(getSpacingAdd(), getSpacingMultiplier())
-                .setUseLineSpacingFromFallbacks(mFallbackLineSpacing)
-                .setEllipsizedWidth(mEllipsizedWidth)
-                .setEllipsize(mEllipsizeAt)
-                .setBreakStrategy(mBreakStrategy)
-                .setHyphenationFrequency(mHyphenationFrequency)
-                .setJustificationMode(mJustificationMode)
-                .setLineBreakConfig(mLineBreakConfig)
-                .setAddLastLineLineSpacing(!islast)
-                .setIncludePad(false)
-                .setUseBoundsForWidth(mUseBoundsForWidth)
-                .setShiftDrawingOffsetForStartOverhang(mShiftDrawingOffsetForStartOverhang)
-                .setMinimumFontMetrics(mMinimumFontMetrics)
-                .setCalculateBounds(true);
-
-        reflowed = b.buildPartialStaticLayoutForDynamicLayout(true /* trackpadding */, reflowed);
-        int n = reflowed.getLineCount();
-        // If the new layout has a blank line at the end, but it is not
-        // the very end of the buffer, then we already have a line that
-        // starts there, so disregard the blank line.
-
-        if (where + after != len && reflowed.getLineStart(n - 1) == where + after)
-            n--;
-
-        // remove affected lines from old layout
-        mInts.deleteAt(startline, endline - startline);
-        mObjects.deleteAt(startline, endline - startline);
-
-        // adjust offsets in layout for new height and offsets
-
-        int ht = reflowed.getLineTop(n);
-        int toppad = 0, botpad = 0;
-
-        if (mIncludePad && startline == 0) {
-            toppad = reflowed.getTopPadding();
-            mTopPadding = toppad;
-            ht -= toppad;
-        }
-        if (mIncludePad && islast) {
-            botpad = reflowed.getBottomPadding();
-            mBottomPadding = botpad;
-            ht += botpad;
-        }
-
-        mInts.adjustValuesBelow(startline, START, after - before);
-        mInts.adjustValuesBelow(startline, TOP, startv - endv + ht);
-
-        // insert new layout
-
-        int[] ints;
-
-        if (mEllipsize) {
-            ints = new int[COLUMNS_ELLIPSIZE];
-            ints[ELLIPSIS_START] = ELLIPSIS_UNDEFINED;
-        } else {
-            ints = new int[COLUMNS_NORMAL];
-        }
-
-        Directions[] objects = new Directions[1];
-
-        for (int i = 0; i < n; i++) {
-            final int start = reflowed.getLineStart(i);
-            ints[START] = start;
-            ints[DIR] |= reflowed.getParagraphDirection(i) << DIR_SHIFT;
-            ints[TAB] |= reflowed.getLineContainsTab(i) ? TAB_MASK : 0;
-
-            int top = reflowed.getLineTop(i) + startv;
-            if (i > 0)
-                top -= toppad;
-            ints[TOP] = top;
-
-            int desc = reflowed.getLineDescent(i);
-            if (i == n - 1)
-                desc += botpad;
-
-            ints[DESCENT] = desc;
-            ints[EXTRA] = reflowed.getLineExtra(i);
-            objects[0] = reflowed.getLineDirections(i);
-
-            final int end = (i == n - 1) ? where + after : reflowed.getLineStart(i + 1);
-            ints[HYPHEN] = StaticLayout.packHyphenEdit(
-                    reflowed.getStartHyphenEdit(i), reflowed.getEndHyphenEdit(i));
-            ints[MAY_PROTRUDE_FROM_TOP_OR_BOTTOM] |=
-                    contentMayProtrudeFromLineTopOrBottom(text, start, end) ?
-                            MAY_PROTRUDE_FROM_TOP_OR_BOTTOM_MASK : 0;
-
-            if (mEllipsize) {
-                ints[ELLIPSIS_START] = reflowed.getEllipsisStart(i);
-                ints[ELLIPSIS_COUNT] = reflowed.getEllipsisCount(i);
+        try {
+            if (s != mBase) {
+                return;
             }
 
-            mInts.insertAt(startline + i, ints);
-            mObjects.insertAt(startline + i, objects);
-        }
+            CharSequence text = mDisplay;
+            int len = text.length();
 
-        updateBlocks(startline, endline - 1, n);
+            // seek back to the start of the paragraph
 
-        b.finish();
-        synchronized (sLock) {
-            sStaticLayout = reflowed;
-            sBuilder = b;
+            int find = TextUtils.lastIndexOf(text, '\n', where - 1);
+            if (find < 0) {
+                find = 0;
+            } else {
+                find = find + 1;
+            }
+
+            {
+                int diff = where - find;
+                before += diff;
+                after += diff;
+                where -= diff;
+            }
+
+            // seek forward to the end of the paragraph
+
+            int look = TextUtils.indexOf(text, '\n', where + after);
+            if (look < 0) {
+                look = len;
+            } else {
+                look++; // we want the index after the \n
+            }
+
+            int change = look - (where + after);
+            before += change;
+            after += change;
+
+            // seek further out to cover anything that is forced to wrap together
+
+            if (text instanceof Spanned) {
+                Spanned sp = (Spanned) text;
+                boolean again;
+
+                do {
+                    again = false;
+
+                    Object[] force = sp.getSpans(where, where + after,
+                            WrapTogetherSpan.class);
+
+                    for (int i = 0; i < force.length; i++) {
+                        int st = sp.getSpanStart(force[i]);
+                        int en = sp.getSpanEnd(force[i]);
+
+                        if (st < where) {
+                            again = true;
+
+                            int diff = where - st;
+                            before += diff;
+                            after += diff;
+                            where -= diff;
+                        }
+
+                        if (en > where + after) {
+                            again = true;
+
+                            int diff = en - (where + after);
+                            before += diff;
+                            after += diff;
+                        }
+                    }
+                } while (again);
+            }
+
+            // find affected region of old layout
+
+            int startline = getLineForOffset(where);
+            int startv = getLineTop(startline);
+
+            int endline = getLineForOffset(where + before);
+            if (where + after == len) {
+                endline = getLineCount();
+            }
+            int endv = getLineTop(endline);
+            boolean islast = (endline == getLineCount());
+
+            // generate new layout for affected text
+
+            StaticLayout reflowed;
+            StaticLayout.Builder b;
+
+            synchronized (sLock) {
+                reflowed = sStaticLayout;
+                b = sBuilder;
+                sStaticLayout = null;
+                sBuilder = null;
+            }
+
+            if (b == null) {
+                b = StaticLayout.Builder.obtain(text, where, where + after, getPaint(), getWidth());
+            }
+
+            b.setText(text, where, where + after)
+                    .setPaint(getPaint())
+                    .setWidth(getWidth())
+                    .setTextDirection(getTextDirectionHeuristic())
+                    .setLineSpacing(getSpacingAdd(), getSpacingMultiplier())
+                    .setUseLineSpacingFromFallbacks(mFallbackLineSpacing)
+                    .setEllipsizedWidth(mEllipsizedWidth)
+                    .setEllipsize(mEllipsizeAt)
+                    .setBreakStrategy(mBreakStrategy)
+                    .setHyphenationFrequency(mHyphenationFrequency)
+                    .setJustificationMode(mJustificationMode)
+                    .setLineBreakConfig(mLineBreakConfig)
+                    .setAddLastLineLineSpacing(!islast)
+                    .setIncludePad(false)
+                    .setUseBoundsForWidth(mUseBoundsForWidth)
+                    .setShiftDrawingOffsetForStartOverhang(mShiftDrawingOffsetForStartOverhang)
+                    .setMinimumFontMetrics(mMinimumFontMetrics)
+                    .setCalculateBounds(true);
+
+            reflowed = b.buildPartialStaticLayoutForDynamicLayout(true /* trackpadding */,
+                    reflowed);
+            int n = reflowed.getLineCount();
+            // If the new layout has a blank line at the end, but it is not
+            // the very end of the buffer, then we already have a line that
+            // starts there, so disregard the blank line.
+
+            if (where + after != len && reflowed.getLineStart(n - 1) == where + after) {
+                n--;
+            }
+
+            // remove affected lines from old layout
+            mInts.deleteAt(startline, endline - startline);
+            mObjects.deleteAt(startline, endline - startline);
+
+            // adjust offsets in layout for new height and offsets
+
+            int ht = reflowed.getLineTop(n);
+            int toppad = 0, botpad = 0;
+
+            if (mIncludePad && startline == 0) {
+                toppad = reflowed.getTopPadding();
+                mTopPadding = toppad;
+                ht -= toppad;
+            }
+            if (mIncludePad && islast) {
+                botpad = reflowed.getBottomPadding();
+                mBottomPadding = botpad;
+                ht += botpad;
+            }
+
+            mInts.adjustValuesBelow(startline, START, after - before);
+            mInts.adjustValuesBelow(startline, TOP, startv - endv + ht);
+
+            // insert new layout
+
+            int[] ints;
+
+            if (mEllipsize) {
+                ints = new int[COLUMNS_ELLIPSIZE];
+                ints[ELLIPSIS_START] = ELLIPSIS_UNDEFINED;
+            } else {
+                ints = new int[COLUMNS_NORMAL];
+            }
+
+            Directions[] objects = new Directions[1];
+
+            for (int i = 0; i < n; i++) {
+                final int start = reflowed.getLineStart(i);
+                ints[START] = start;
+                ints[DIR] |= reflowed.getParagraphDirection(i) << DIR_SHIFT;
+                ints[TAB] |= reflowed.getLineContainsTab(i) ? TAB_MASK : 0;
+
+                int top = reflowed.getLineTop(i) + startv;
+                if (i > 0) {
+                    top -= toppad;
+                }
+                ints[TOP] = top;
+
+                int desc = reflowed.getLineDescent(i);
+                if (i == n - 1) {
+                    desc += botpad;
+                }
+
+                ints[DESCENT] = desc;
+                ints[EXTRA] = reflowed.getLineExtra(i);
+                objects[0] = reflowed.getLineDirections(i);
+
+                final int end = (i == n - 1) ? where + after : reflowed.getLineStart(i + 1);
+                ints[HYPHEN] = StaticLayout.packHyphenEdit(
+                        reflowed.getStartHyphenEdit(i), reflowed.getEndHyphenEdit(i));
+                ints[MAY_PROTRUDE_FROM_TOP_OR_BOTTOM] |=
+                        contentMayProtrudeFromLineTopOrBottom(text, start, end)
+                                ? MAY_PROTRUDE_FROM_TOP_OR_BOTTOM_MASK : 0;
+
+                if (mEllipsize) {
+                    ints[ELLIPSIS_START] = reflowed.getEllipsisStart(i);
+                    ints[ELLIPSIS_COUNT] = reflowed.getEllipsisCount(i);
+                }
+
+                mInts.insertAt(startline + i, ints);
+                mObjects.insertAt(startline + i, objects);
+            }
+
+            updateBlocks(startline, endline - 1, n);
+
+            b.finish();
+            synchronized (sLock) {
+                sStaticLayout = reflowed;
+                sBuilder = b;
+            }
+        } finally {
+            if (TRACE_LAYOUT) {
+                Trace.endSection();
+            }
         }
     }
 
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 8dee4b1..ce238a7 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -39,6 +39,7 @@
 import android.graphics.text.LineBreakConfig;
 import android.graphics.text.LineBreaker;
 import android.os.Build;
+import android.os.Trace;
 import android.text.method.TextKeyListener;
 import android.text.style.AlignmentSpan;
 import android.text.style.LeadingMarginSpan;
@@ -70,6 +71,11 @@
  * For text that will not change, use a {@link StaticLayout}.
  */
 public abstract class Layout {
+
+    /** @hide */
+    protected static final boolean TRACE_LAYOUT = Build.isDebuggable();
+
+
     /** @hide */
     @IntDef(prefix = { "BREAK_STRATEGY_" }, value = {
             LineBreaker.BREAK_STRATEGY_SIMPLE,
@@ -472,40 +478,51 @@
             @Nullable Path selectionPath,
             @Nullable Paint selectionPaint,
             int cursorOffsetVertical) {
-        float leftShift = 0;
-        if (mUseBoundsForWidth && mShiftDrawingOffsetForStartOverhang) {
-            RectF drawingRect = computeDrawingBoundingBox();
-            if (drawingRect.left < 0) {
-                leftShift = -drawingRect.left;
-                canvas.translate(leftShift, 0);
+        if (TRACE_LAYOUT) {
+            Trace.beginSection("Layout#draw");
+        }
+        try {
+            float leftShift = 0;
+            if (mUseBoundsForWidth && mShiftDrawingOffsetForStartOverhang) {
+                RectF drawingRect = computeDrawingBoundingBox();
+                if (drawingRect.left < 0) {
+                    leftShift = -drawingRect.left;
+                    canvas.translate(leftShift, 0);
+                }
             }
-        }
-        final long lineRange = getLineRangeForDraw(canvas);
-        int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
-        int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
-        if (lastLine < 0) return;
+            final long lineRange = getLineRangeForDraw(canvas);
+            int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
+            int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
+            if (lastLine < 0) return;
 
-        if (shouldDrawHighlightsOnTop(canvas)) {
-            drawBackground(canvas, firstLine, lastLine);
-        } else {
-            drawWithoutText(canvas, highlightPaths, highlightPaints, selectionPath, selectionPaint,
-                    cursorOffsetVertical, firstLine, lastLine);
-        }
+            if (shouldDrawHighlightsOnTop(canvas)) {
+                drawBackground(canvas, firstLine, lastLine);
+            } else {
+                drawWithoutText(canvas, highlightPaths, highlightPaints, selectionPath,
+                        selectionPaint,
+                        cursorOffsetVertical, firstLine, lastLine);
+            }
 
-        drawText(canvas, firstLine, lastLine);
+            drawText(canvas, firstLine, lastLine);
 
-        // Since high contrast text draws a solid rectangle background behind the text, it covers up
-        // the highlights and selections. In this case we draw over the top of the text with a
-        // blend mode that ensures the text stays high-contrast.
-        if (shouldDrawHighlightsOnTop(canvas)) {
-            drawHighlights(canvas, highlightPaths, highlightPaints, selectionPath, selectionPaint,
-                    cursorOffsetVertical, firstLine, lastLine);
-        }
+            // Since high contrast text draws a solid rectangle background behind the text, it
+            // covers up the highlights and selections. In this case we draw over the top of the
+            // text with a blend mode that ensures the text stays high-contrast.
+            if (shouldDrawHighlightsOnTop(canvas)) {
+                drawHighlights(canvas, highlightPaths, highlightPaints, selectionPath,
+                        selectionPaint,
+                        cursorOffsetVertical, firstLine, lastLine);
+            }
 
-        if (leftShift != 0) {
-            // Manually translate back to the original position because of b/324498002, using
-            // save/restore disappears the toggle switch drawables.
-            canvas.translate(-leftShift, 0);
+            if (leftShift != 0) {
+                // Manually translate back to the original position because of b/324498002, using
+                // save/restore disappears the toggle switch drawables.
+                canvas.translate(-leftShift, 0);
+            }
+        } finally {
+            if (TRACE_LAYOUT) {
+                Trace.endSection();
+            }
         }
     }
 
diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java
index 5f6a9bd..14401a6 100644
--- a/core/java/android/text/PrecomputedText.java
+++ b/core/java/android/text/PrecomputedText.java
@@ -25,6 +25,8 @@
 import android.graphics.Rect;
 import android.graphics.text.LineBreakConfig;
 import android.graphics.text.MeasuredText;
+import android.os.Build;
+import android.os.Trace;
 import android.text.style.MetricAffectingSpan;
 
 import com.android.internal.util.Preconditions;
@@ -78,6 +80,8 @@
 public class PrecomputedText implements Spannable {
     private static final char LINE_FEED = '\n';
 
+    private static final boolean TRACE_PCT = Build.isDebuggable();
+
     /**
      * The information required for building {@link PrecomputedText}.
      *
@@ -447,35 +451,47 @@
 
     private static ParagraphInfo[] createMeasuredParagraphsFromPrecomputedText(
             @NonNull PrecomputedText pct, @NonNull Params params, boolean computeLayout) {
-        final boolean needHyphenation = params.getBreakStrategy() != Layout.BREAK_STRATEGY_SIMPLE
-                && params.getHyphenationFrequency() != Layout.HYPHENATION_FREQUENCY_NONE;
-        final int hyphenationMode;
-        if (needHyphenation) {
-            hyphenationMode = isFastHyphenation(params.getHyphenationFrequency())
-                    ? MeasuredText.Builder.HYPHENATION_MODE_FAST :
-                    MeasuredText.Builder.HYPHENATION_MODE_NORMAL;
-        } else {
-            hyphenationMode = MeasuredText.Builder.HYPHENATION_MODE_NONE;
+        if (TRACE_PCT) {
+            Trace.beginSection("PrecomputedText#createMeasuredParagraphsFromPrecomputedText");
+            Trace.setCounter("PrecomputedText#textCharCount", pct.length());
         }
-        LineBreakConfig config = params.getLineBreakConfig();
-        if (config.getLineBreakWordStyle() == LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO
-                && pct.getParagraphCount() != 1) {
-            // If the text has multiple paragraph, resolve line break word style auto to none.
-            config = new LineBreakConfig.Builder()
-                    .merge(config)
-                    .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
-                    .build();
+        try {
+            final boolean needHyphenation =
+                    params.getBreakStrategy() != Layout.BREAK_STRATEGY_SIMPLE
+                            && params.getHyphenationFrequency()
+                            != Layout.HYPHENATION_FREQUENCY_NONE;
+            final int hyphenationMode;
+            if (needHyphenation) {
+                hyphenationMode = isFastHyphenation(params.getHyphenationFrequency())
+                        ? MeasuredText.Builder.HYPHENATION_MODE_FAST :
+                        MeasuredText.Builder.HYPHENATION_MODE_NORMAL;
+            } else {
+                hyphenationMode = MeasuredText.Builder.HYPHENATION_MODE_NONE;
+            }
+            LineBreakConfig config = params.getLineBreakConfig();
+            if (config.getLineBreakWordStyle() == LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO
+                    && pct.getParagraphCount() != 1) {
+                // If the text has multiple paragraph, resolve line break word style auto to none.
+                config = new LineBreakConfig.Builder()
+                        .merge(config)
+                        .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
+                        .build();
+            }
+            ArrayList<ParagraphInfo> result = new ArrayList<>();
+            for (int i = 0; i < pct.getParagraphCount(); ++i) {
+                final int paraStart = pct.getParagraphStart(i);
+                final int paraEnd = pct.getParagraphEnd(i);
+                result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout(
+                        params.getTextPaint(), config, pct, paraStart, paraEnd,
+                        params.getTextDirection(), hyphenationMode, computeLayout, true,
+                        pct.getMeasuredParagraph(i), null /* no recycle */)));
+            }
+            return result.toArray(new ParagraphInfo[result.size()]);
+        } finally {
+            if (TRACE_PCT) {
+                Trace.endSection();
+            }
         }
-        ArrayList<ParagraphInfo> result = new ArrayList<>();
-        for (int i = 0; i < pct.getParagraphCount(); ++i) {
-            final int paraStart = pct.getParagraphStart(i);
-            final int paraEnd = pct.getParagraphEnd(i);
-            result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout(
-                    params.getTextPaint(), config, pct, paraStart, paraEnd,
-                    params.getTextDirection(), hyphenationMode, computeLayout, true,
-                    pct.getMeasuredParagraph(i), null /* no recycle */)));
-        }
-        return result.toArray(new ParagraphInfo[result.size()]);
     }
 
     /** @hide */
@@ -483,53 +499,65 @@
             @NonNull CharSequence text, @NonNull Params params,
             @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean computeLayout,
             boolean computeBounds) {
-        ArrayList<ParagraphInfo> result = new ArrayList<>();
-
-        Preconditions.checkNotNull(text);
-        Preconditions.checkNotNull(params);
-        final boolean needHyphenation = params.getBreakStrategy() != Layout.BREAK_STRATEGY_SIMPLE
-                && params.getHyphenationFrequency() != Layout.HYPHENATION_FREQUENCY_NONE;
-        final int hyphenationMode;
-        if (needHyphenation) {
-            hyphenationMode = isFastHyphenation(params.getHyphenationFrequency())
-                    ? MeasuredText.Builder.HYPHENATION_MODE_FAST :
-                    MeasuredText.Builder.HYPHENATION_MODE_NORMAL;
-        } else {
-            hyphenationMode = MeasuredText.Builder.HYPHENATION_MODE_NONE;
+        if (TRACE_PCT) {
+            Trace.beginSection("PrecomputedText#createMeasuredParagraphs");
+            Trace.setCounter("PrecomputedText#textCharCount", text.length());
         }
+        try {
+            ArrayList<ParagraphInfo> result = new ArrayList<>();
 
-        LineBreakConfig config = null;
-        int paraEnd = 0;
-        for (int paraStart = start; paraStart < end; paraStart = paraEnd) {
-            paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end);
-            if (paraEnd < 0) {
-                // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph
-                // end.
-                paraEnd = end;
+            Preconditions.checkNotNull(text);
+            Preconditions.checkNotNull(params);
+            final boolean needHyphenation =
+                    params.getBreakStrategy() != Layout.BREAK_STRATEGY_SIMPLE
+                            && params.getHyphenationFrequency()
+                            != Layout.HYPHENATION_FREQUENCY_NONE;
+            final int hyphenationMode;
+            if (needHyphenation) {
+                hyphenationMode = isFastHyphenation(params.getHyphenationFrequency())
+                        ? MeasuredText.Builder.HYPHENATION_MODE_FAST :
+                        MeasuredText.Builder.HYPHENATION_MODE_NORMAL;
             } else {
-                paraEnd++;  // Includes LINE_FEED(U+000A) to the prev paragraph.
+                hyphenationMode = MeasuredText.Builder.HYPHENATION_MODE_NONE;
             }
 
-            if (config == null) {
-                config = params.getLineBreakConfig();
-                if (config.getLineBreakWordStyle() == LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO
-                        && !(paraStart == start && paraEnd == end)) {
-                    // If the text has multiple paragraph, resolve line break word style auto to
-                    // none.
-                    config = new LineBreakConfig.Builder()
-                            .merge(config)
-                            .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
-                            .build();
+            LineBreakConfig config = null;
+            int paraEnd = 0;
+            for (int paraStart = start; paraStart < end; paraStart = paraEnd) {
+                paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end);
+                if (paraEnd < 0) {
+                    // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph
+                    // end.
+                    paraEnd = end;
+                } else {
+                    paraEnd++;  // Includes LINE_FEED(U+000A) to the prev paragraph.
                 }
-            }
 
-            result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout(
-                    params.getTextPaint(), config, text, paraStart, paraEnd,
-                    params.getTextDirection(), hyphenationMode, computeLayout, computeBounds,
-                    null /* no hint */,
-                    null /* no recycle */)));
+                if (config == null) {
+                    config = params.getLineBreakConfig();
+                    if (config.getLineBreakWordStyle() == LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO
+                            && !(paraStart == start && paraEnd == end)) {
+                        // If the text has multiple paragraph, resolve line break word style auto to
+                        // none.
+                        config = new LineBreakConfig.Builder()
+                                .merge(config)
+                                .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
+                                .build();
+                    }
+                }
+
+                result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout(
+                        params.getTextPaint(), config, text, paraStart, paraEnd,
+                        params.getTextDirection(), hyphenationMode, computeLayout, computeBounds,
+                        null /* no hint */,
+                        null /* no recycle */)));
+            }
+            return result.toArray(new ParagraphInfo[result.size()]);
+        } finally {
+            if (TRACE_PCT) {
+                Trace.endSection();
+            }
         }
-        return result.toArray(new ParagraphInfo[result.size()]);
     }
 
     // Use PrecomputedText.create instead.
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 3dd3a9e..d1b14d1 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -542,10 +542,20 @@
          */
         @NonNull
         public StaticLayout build() {
-            StaticLayout result = new StaticLayout(this, mIncludePad, mEllipsize != null
-                    ? COLUMNS_ELLIPSIZE : COLUMNS_NORMAL);
-            Builder.recycle(this);
-            return result;
+            if (TRACE_LAYOUT) {
+                Trace.beginSection("StaticLayout#build");
+                Trace.setCounter("StaticLayout#textLength", mText.length());
+            }
+            try {
+                StaticLayout result = new StaticLayout(this, mIncludePad, mEllipsize != null
+                        ? COLUMNS_ELLIPSIZE : COLUMNS_NORMAL);
+                Builder.recycle(this);
+                return result;
+            } finally {
+                if (TRACE_LAYOUT) {
+                    Trace.endSection();
+                }
+            }
         }
 
         /**
@@ -562,16 +572,21 @@
          */
         /* package */ @NonNull StaticLayout buildPartialStaticLayoutForDynamicLayout(
                 boolean trackpadding, StaticLayout recycle) {
-            if (recycle == null) {
-                recycle = new StaticLayout();
+            if (TRACE_LAYOUT) {
+                Trace.beginSection("StaticLayout#forDynamicLayout");
+                Trace.setCounter("StaticLayout#textLength", mText.length());
             }
-            Trace.beginSection("Generating StaticLayout For DynamicLayout");
             try {
+                if (recycle == null) {
+                    recycle = new StaticLayout();
+                }
                 recycle.generate(this, mIncludePad, trackpadding);
+                return recycle;
             } finally {
-                Trace.endSection();
+                if (TRACE_LAYOUT) {
+                    Trace.endSection();
+                }
             }
-            return recycle;
         }
 
         private CharSequence mText;
@@ -727,12 +742,7 @@
         mLeftIndents = b.mLeftIndents;
         mRightIndents = b.mRightIndents;
 
-        Trace.beginSection("Constructing StaticLayout");
-        try {
-            generate(b, b.mIncludePad, trackPadding);
-        } finally {
-            Trace.endSection();
-        }
+        generate(b, b.mIncludePad, trackPadding);
     }
 
     private static int getBaseHyphenationFrequency(int frequency) {
@@ -842,14 +852,23 @@
                 case PrecomputedText.Params.UNUSABLE:
                     break;
                 case PrecomputedText.Params.NEED_RECOMPUTE:
-                    final PrecomputedText.Params newParams =
-                            new PrecomputedText.Params.Builder(paint)
-                                .setBreakStrategy(b.mBreakStrategy)
-                                .setHyphenationFrequency(b.mHyphenationFrequency)
-                                .setTextDirection(textDir)
-                                .setLineBreakConfig(b.mLineBreakConfig)
-                                .build();
-                    precomputed = PrecomputedText.create(precomputed, newParams);
+                    if (TRACE_LAYOUT) {
+                        Trace.beginSection("StaticLayout#recomputePct");
+                    }
+                    try {
+                        final PrecomputedText.Params newParams =
+                                new PrecomputedText.Params.Builder(paint)
+                                    .setBreakStrategy(b.mBreakStrategy)
+                                    .setHyphenationFrequency(b.mHyphenationFrequency)
+                                    .setTextDirection(textDir)
+                                    .setLineBreakConfig(b.mLineBreakConfig)
+                                    .build();
+                        precomputed = PrecomputedText.create(precomputed, newParams);
+                    } finally {
+                        if (TRACE_LAYOUT) {
+                            Trace.endSection();
+                        }
+                    }
                     paragraphInfo = precomputed.getParagraphInfo();
                     break;
                 case PrecomputedText.Params.USABLE:
@@ -860,232 +879,261 @@
         }
 
         if (paragraphInfo == null) {
-            final PrecomputedText.Params param = new PrecomputedText.Params(paint,
-                    b.mLineBreakConfig, textDir, b.mBreakStrategy, b.mHyphenationFrequency);
-            paragraphInfo = PrecomputedText.createMeasuredParagraphs(source, param, bufStart,
-                    bufEnd, false /* computeLayout */, b.mCalculateBounds);
+            if (TRACE_LAYOUT) {
+                Trace.beginSection("StaticLayout#computePct");
+            }
+            try {
+                final PrecomputedText.Params param = new PrecomputedText.Params(paint,
+                        b.mLineBreakConfig, textDir, b.mBreakStrategy, b.mHyphenationFrequency);
+                paragraphInfo = PrecomputedText.createMeasuredParagraphs(source, param, bufStart,
+                        bufEnd, false /* computeLayout */, b.mCalculateBounds);
+            } finally {
+                if (TRACE_LAYOUT) {
+                    Trace.endSection();
+                }
+            }
         }
 
         for (int paraIndex = 0; paraIndex < paragraphInfo.length; paraIndex++) {
-            final int paraStart = paraIndex == 0
-                    ? bufStart : paragraphInfo[paraIndex - 1].paragraphEnd;
-            final int paraEnd = paragraphInfo[paraIndex].paragraphEnd;
+            if (TRACE_LAYOUT) {
+                Trace.beginSection("StaticLayout#processParagraph");
+                Trace.setCounter("StaticLayout#paragraph", paraIndex);
+            }
+            try {
+                final int paraStart = paraIndex == 0
+                        ? bufStart : paragraphInfo[paraIndex - 1].paragraphEnd;
+                final int paraEnd = paragraphInfo[paraIndex].paragraphEnd;
 
-            int firstWidthLineCount = 1;
-            int firstWidth = outerWidth;
-            int restWidth = outerWidth;
+                int firstWidthLineCount = 1;
+                int firstWidth = outerWidth;
+                int restWidth = outerWidth;
 
-            LineHeightSpan[] chooseHt = null;
-            if (spanned != null) {
-                LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
-                        LeadingMarginSpan.class);
-                for (int i = 0; i < sp.length; i++) {
-                    LeadingMarginSpan lms = sp[i];
-                    firstWidth -= sp[i].getLeadingMargin(true);
-                    restWidth -= sp[i].getLeadingMargin(false);
+                LineHeightSpan[] chooseHt = null;
+                if (spanned != null) {
+                    LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
+                            LeadingMarginSpan.class);
+                    for (int i = 0; i < sp.length; i++) {
+                        LeadingMarginSpan lms = sp[i];
+                        firstWidth -= sp[i].getLeadingMargin(true);
+                        restWidth -= sp[i].getLeadingMargin(false);
 
-                    // LeadingMarginSpan2 is odd.  The count affects all
-                    // leading margin spans, not just this particular one
-                    if (lms instanceof LeadingMarginSpan2) {
-                        LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
-                        firstWidthLineCount = Math.max(firstWidthLineCount,
-                                lms2.getLeadingMarginLineCount());
+                        // LeadingMarginSpan2 is odd.  The count affects all
+                        // leading margin spans, not just this particular one
+                        if (lms instanceof LeadingMarginSpan2) {
+                            LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
+                            firstWidthLineCount = Math.max(firstWidthLineCount,
+                                    lms2.getLeadingMarginLineCount());
+                        }
+                    }
+
+                    chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
+
+                    if (chooseHt.length == 0) {
+                        chooseHt = null; // So that out() would not assume it has any contents
+                    } else {
+                        if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
+                            chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
+                        }
+
+                        for (int i = 0; i < chooseHt.length; i++) {
+                            int o = spanned.getSpanStart(chooseHt[i]);
+
+                            if (o < paraStart) {
+                                // starts in this layout, before the
+                                // current paragraph
+
+                                chooseHtv[i] = getLineTop(getLineForOffset(o));
+                            } else {
+                                // starts in this paragraph
+
+                                chooseHtv[i] = v;
+                            }
+                        }
+                    }
+                }
+                // tab stop locations
+                float[] variableTabStops = null;
+                if (spanned != null) {
+                    TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
+                            paraEnd, TabStopSpan.class);
+                    if (spans.length > 0) {
+                        float[] stops = new float[spans.length];
+                        for (int i = 0; i < spans.length; i++) {
+                            stops[i] = (float) spans[i].getTabStop();
+                        }
+                        Arrays.sort(stops, 0, stops.length);
+                        variableTabStops = stops;
                     }
                 }
 
-                chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
+                final MeasuredParagraph measuredPara = paragraphInfo[paraIndex].measured;
+                final char[] chs = measuredPara.getChars();
+                final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
+                final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
 
-                if (chooseHt.length == 0) {
-                    chooseHt = null; // So that out() would not assume it has any contents
-                } else {
-                    if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
-                        chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
+                constraints.setWidth(restWidth);
+                constraints.setIndent(firstWidth, firstWidthLineCount);
+                constraints.setTabStops(variableTabStops, TAB_INCREMENT);
+
+                if (TRACE_LAYOUT) {
+                    Trace.beginSection("LineBreaker#computeLineBreaks");
+                }
+                LineBreaker.Result res;
+                try {
+                    res = lineBreaker.computeLineBreaks(
+                            measuredPara.getMeasuredText(), constraints, mLineCount);
+                } finally {
+                    if (TRACE_LAYOUT) {
+                        Trace.endSection();
                     }
+                }
+                int breakCount = res.getLineCount();
+                if (lineBreakCapacity < breakCount) {
+                    lineBreakCapacity = breakCount;
+                    breaks = new int[lineBreakCapacity];
+                    lineWidths = new float[lineBreakCapacity];
+                    ascents = new float[lineBreakCapacity];
+                    descents = new float[lineBreakCapacity];
+                    hasTabs = new boolean[lineBreakCapacity];
+                    hyphenEdits = new int[lineBreakCapacity];
+                }
 
-                    for (int i = 0; i < chooseHt.length; i++) {
-                        int o = spanned.getSpanStart(chooseHt[i]);
+                for (int i = 0; i < breakCount; ++i) {
+                    breaks[i] = res.getLineBreakOffset(i);
+                    lineWidths[i] = res.getLineWidth(i);
+                    ascents[i] = res.getLineAscent(i);
+                    descents[i] = res.getLineDescent(i);
+                    hasTabs[i] = res.hasLineTab(i);
+                    hyphenEdits[i] =
+                        packHyphenEdit(res.getStartLineHyphenEdit(i), res.getEndLineHyphenEdit(i));
+                }
 
-                        if (o < paraStart) {
-                            // starts in this layout, before the
-                            // current paragraph
-
-                            chooseHtv[i] = getLineTop(getLineForOffset(o));
+                final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
+                final boolean ellipsisMayBeApplied = ellipsize != null
+                        && (ellipsize == TextUtils.TruncateAt.END
+                            || (mMaximumVisibleLineCount == 1
+                                    && ellipsize != TextUtils.TruncateAt.MARQUEE));
+                if (0 < remainingLineCount && remainingLineCount < breakCount
+                        && ellipsisMayBeApplied) {
+                    // Calculate width
+                    float width = 0;
+                    boolean hasTab = false;  // XXX May need to also have starting hyphen edit
+                    for (int i = remainingLineCount - 1; i < breakCount; i++) {
+                        if (i == breakCount - 1) {
+                            width += lineWidths[i];
                         } else {
-                            // starts in this paragraph
+                            for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
+                                width += measuredPara.getCharWidthAt(j);
+                            }
+                        }
+                        hasTab |= hasTabs[i];
+                    }
+                    // Treat the last line and overflowed lines as a single line.
+                    breaks[remainingLineCount - 1] = breaks[breakCount - 1];
+                    lineWidths[remainingLineCount - 1] = width;
+                    hasTabs[remainingLineCount - 1] = hasTab;
 
-                            chooseHtv[i] = v;
+                    breakCount = remainingLineCount;
+                }
+
+                // here is the offset of the starting character of the line we are currently
+                // measuring
+                int here = paraStart;
+
+                int fmTop = defaultTop;
+                int fmBottom = defaultBottom;
+                int fmAscent = defaultAscent;
+                int fmDescent = defaultDescent;
+                int fmCacheIndex = 0;
+                int spanEndCacheIndex = 0;
+                int breakIndex = 0;
+                for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
+                    // retrieve end of span
+                    spanEnd = spanEndCache[spanEndCacheIndex++];
+
+                    // retrieve cached metrics, order matches above
+                    fm.top = fmCache[fmCacheIndex * 4 + 0];
+                    fm.bottom = fmCache[fmCacheIndex * 4 + 1];
+                    fm.ascent = fmCache[fmCacheIndex * 4 + 2];
+                    fm.descent = fmCache[fmCacheIndex * 4 + 3];
+                    fmCacheIndex++;
+
+                    if (fm.top < fmTop) {
+                        fmTop = fm.top;
+                    }
+                    if (fm.ascent < fmAscent) {
+                        fmAscent = fm.ascent;
+                    }
+                    if (fm.descent > fmDescent) {
+                        fmDescent = fm.descent;
+                    }
+                    if (fm.bottom > fmBottom) {
+                        fmBottom = fm.bottom;
+                    }
+
+                    // skip breaks ending before current span range
+                    while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
+                        breakIndex++;
+                    }
+
+                    while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
+                        int endPos = paraStart + breaks[breakIndex];
+
+                        boolean moreChars = (endPos < bufEnd);
+
+                        final int ascent = isFallbackLineSpacing
+                                ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
+                                : fmAscent;
+                        final int descent = isFallbackLineSpacing
+                                ? Math.max(fmDescent, Math.round(descents[breakIndex]))
+                                : fmDescent;
+
+                        // The fallback ascent/descent may be larger than top/bottom of the default
+                        // font metrics. Adjust top/bottom with ascent/descent for avoiding
+                        // unexpected clipping.
+                        if (isFallbackLineSpacing) {
+                            if (ascent < fmTop) {
+                                fmTop = ascent;
+                            }
+                            if (descent > fmBottom) {
+                                fmBottom = descent;
+                            }
+                        }
+
+                        v = out(source, here, endPos,
+                                ascent, descent, fmTop, fmBottom,
+                                v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
+                                hasTabs[breakIndex], hyphenEdits[breakIndex], needMultiply,
+                                measuredPara, bufEnd, includepad, trackpad, addLastLineSpacing, chs,
+                                paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
+                                paint, moreChars);
+
+                        if (endPos < spanEnd) {
+                            // preserve metrics for current span
+                            fmTop = Math.min(defaultTop, fm.top);
+                            fmBottom = Math.max(defaultBottom, fm.bottom);
+                            fmAscent = Math.min(defaultAscent, fm.ascent);
+                            fmDescent = Math.max(defaultDescent, fm.descent);
+                        } else {
+                            fmTop = fmBottom = fmAscent = fmDescent = 0;
+                        }
+
+                        here = endPos;
+                        breakIndex++;
+
+                        if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
+                            return;
                         }
                     }
                 }
-            }
-            // tab stop locations
-            float[] variableTabStops = null;
-            if (spanned != null) {
-                TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
-                        paraEnd, TabStopSpan.class);
-                if (spans.length > 0) {
-                    float[] stops = new float[spans.length];
-                    for (int i = 0; i < spans.length; i++) {
-                        stops[i] = (float) spans[i].getTabStop();
-                    }
-                    Arrays.sort(stops, 0, stops.length);
-                    variableTabStops = stops;
+
+                if (paraEnd == bufEnd) {
+                    break;
                 }
-            }
-
-            final MeasuredParagraph measuredPara = paragraphInfo[paraIndex].measured;
-            final char[] chs = measuredPara.getChars();
-            final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
-            final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
-
-            constraints.setWidth(restWidth);
-            constraints.setIndent(firstWidth, firstWidthLineCount);
-            constraints.setTabStops(variableTabStops, TAB_INCREMENT);
-
-            LineBreaker.Result res = lineBreaker.computeLineBreaks(
-                    measuredPara.getMeasuredText(), constraints, mLineCount);
-            int breakCount = res.getLineCount();
-            if (lineBreakCapacity < breakCount) {
-                lineBreakCapacity = breakCount;
-                breaks = new int[lineBreakCapacity];
-                lineWidths = new float[lineBreakCapacity];
-                ascents = new float[lineBreakCapacity];
-                descents = new float[lineBreakCapacity];
-                hasTabs = new boolean[lineBreakCapacity];
-                hyphenEdits = new int[lineBreakCapacity];
-            }
-
-            for (int i = 0; i < breakCount; ++i) {
-                breaks[i] = res.getLineBreakOffset(i);
-                lineWidths[i] = res.getLineWidth(i);
-                ascents[i] = res.getLineAscent(i);
-                descents[i] = res.getLineDescent(i);
-                hasTabs[i] = res.hasLineTab(i);
-                hyphenEdits[i] =
-                    packHyphenEdit(res.getStartLineHyphenEdit(i), res.getEndLineHyphenEdit(i));
-            }
-
-            final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
-            final boolean ellipsisMayBeApplied = ellipsize != null
-                    && (ellipsize == TextUtils.TruncateAt.END
-                        || (mMaximumVisibleLineCount == 1
-                                && ellipsize != TextUtils.TruncateAt.MARQUEE));
-            if (0 < remainingLineCount && remainingLineCount < breakCount
-                    && ellipsisMayBeApplied) {
-                // Calculate width
-                float width = 0;
-                boolean hasTab = false;  // XXX May need to also have starting hyphen edit
-                for (int i = remainingLineCount - 1; i < breakCount; i++) {
-                    if (i == breakCount - 1) {
-                        width += lineWidths[i];
-                    } else {
-                        for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
-                            width += measuredPara.getCharWidthAt(j);
-                        }
-                    }
-                    hasTab |= hasTabs[i];
+            } finally {
+                if (TRACE_LAYOUT) {
+                    Trace.endSection();
                 }
-                // Treat the last line and overflowed lines as a single line.
-                breaks[remainingLineCount - 1] = breaks[breakCount - 1];
-                lineWidths[remainingLineCount - 1] = width;
-                hasTabs[remainingLineCount - 1] = hasTab;
-
-                breakCount = remainingLineCount;
-            }
-
-            // here is the offset of the starting character of the line we are currently
-            // measuring
-            int here = paraStart;
-
-            int fmTop = defaultTop;
-            int fmBottom = defaultBottom;
-            int fmAscent = defaultAscent;
-            int fmDescent = defaultDescent;
-            int fmCacheIndex = 0;
-            int spanEndCacheIndex = 0;
-            int breakIndex = 0;
-            for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
-                // retrieve end of span
-                spanEnd = spanEndCache[spanEndCacheIndex++];
-
-                // retrieve cached metrics, order matches above
-                fm.top = fmCache[fmCacheIndex * 4 + 0];
-                fm.bottom = fmCache[fmCacheIndex * 4 + 1];
-                fm.ascent = fmCache[fmCacheIndex * 4 + 2];
-                fm.descent = fmCache[fmCacheIndex * 4 + 3];
-                fmCacheIndex++;
-
-                if (fm.top < fmTop) {
-                    fmTop = fm.top;
-                }
-                if (fm.ascent < fmAscent) {
-                    fmAscent = fm.ascent;
-                }
-                if (fm.descent > fmDescent) {
-                    fmDescent = fm.descent;
-                }
-                if (fm.bottom > fmBottom) {
-                    fmBottom = fm.bottom;
-                }
-
-                // skip breaks ending before current span range
-                while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
-                    breakIndex++;
-                }
-
-                while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
-                    int endPos = paraStart + breaks[breakIndex];
-
-                    boolean moreChars = (endPos < bufEnd);
-
-                    final int ascent = isFallbackLineSpacing
-                            ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
-                            : fmAscent;
-                    final int descent = isFallbackLineSpacing
-                            ? Math.max(fmDescent, Math.round(descents[breakIndex]))
-                            : fmDescent;
-
-                    // The fallback ascent/descent may be larger than top/bottom of the default font
-                    // metrics. Adjust top/bottom with ascent/descent for avoiding unexpected
-                    // clipping.
-                    if (isFallbackLineSpacing) {
-                        if (ascent < fmTop) {
-                            fmTop = ascent;
-                        }
-                        if (descent > fmBottom) {
-                            fmBottom = descent;
-                        }
-                    }
-
-                    v = out(source, here, endPos,
-                            ascent, descent, fmTop, fmBottom,
-                            v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
-                            hasTabs[breakIndex], hyphenEdits[breakIndex], needMultiply,
-                            measuredPara, bufEnd, includepad, trackpad, addLastLineSpacing, chs,
-                            paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
-                            paint, moreChars);
-
-                    if (endPos < spanEnd) {
-                        // preserve metrics for current span
-                        fmTop = Math.min(defaultTop, fm.top);
-                        fmBottom = Math.max(defaultBottom, fm.bottom);
-                        fmAscent = Math.min(defaultAscent, fm.ascent);
-                        fmDescent = Math.max(defaultDescent, fm.descent);
-                    } else {
-                        fmTop = fmBottom = fmAscent = fmDescent = 0;
-                    }
-
-                    here = endPos;
-                    breakIndex++;
-
-                    if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
-                        return;
-                    }
-                }
-            }
-
-            if (paraEnd == bufEnd) {
-                break;
             }
         }
 
@@ -1179,9 +1227,18 @@
                     (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
                             ellipsize == TextUtils.TruncateAt.END);
             if (doEllipsis) {
-                calculateEllipsis(start, end, measured, widthStart,
-                        ellipsisWidth, ellipsize, j,
-                        textWidth, paint, forceEllipsis);
+                if (TRACE_LAYOUT) {
+                    Trace.beginSection("StaticLayout#calculateEllipsis");
+                }
+                try {
+                    calculateEllipsis(start, end, measured, widthStart,
+                            ellipsisWidth, ellipsize, j,
+                            textWidth, paint, forceEllipsis);
+                } finally {
+                    if (TRACE_LAYOUT) {
+                        Trace.endSection();
+                    }
+                }
             } else {
                 mLines[mColumns * j + ELLIPSIS_START] = 0;
                 mLines[mColumns * j + ELLIPSIS_COUNT] = 0;
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index bde9c77..a439478 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -28,6 +28,7 @@
 import android.graphics.text.PositionedGlyphs;
 import android.graphics.text.TextRunShaper;
 import android.os.Build;
+import android.os.Trace;
 import android.text.Layout.Directions;
 import android.text.Layout.TabStops;
 import android.text.style.CharacterStyle;
@@ -56,6 +57,8 @@
 public class TextLine {
     private static final boolean DEBUG = false;
 
+    private static final boolean TRACE_TEXTLINE = Build.isDebuggable();
+
     private static final char TAB_CHAR = '\t';
 
     private TextPaint mPaint;
@@ -430,28 +433,37 @@
      * @param bottom the bottom of the line
      */
     void draw(Canvas c, float x, int top, int y, int bottom) {
-        float h = 0;
-        final int runCount = mDirections.getRunCount();
-        for (int runIndex = 0; runIndex < runCount; runIndex++) {
-            final int runStart = mDirections.getRunStart(runIndex);
-            if (runStart > mLen) break;
-            final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
-            final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+        if (TRACE_TEXTLINE) {
+            Trace.beginSection("TextLine#draw");
+        }
+        try {
+            float h = 0;
+            final int runCount = mDirections.getRunCount();
+            for (int runIndex = 0; runIndex < runCount; runIndex++) {
+                final int runStart = mDirections.getRunStart(runIndex);
+                if (runStart > mLen) break;
+                final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
+                final boolean runIsRtl = mDirections.isRunRtl(runIndex);
 
-            final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
+                final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
 
-            int segStart = runStart;
-            for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
-                if (j == runLimit || charAt(j) == TAB_CHAR) {
-                    h += drawRun(c, segStart, j, runIsRtl, x + h, top, y, bottom,
-                            runIndex != (runCount - 1) || j != mLen, runFlag);
+                int segStart = runStart;
+                for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
+                    if (j == runLimit || charAt(j) == TAB_CHAR) {
+                        h += drawRun(c, segStart, j, runIsRtl, x + h, top, y, bottom,
+                                runIndex != (runCount - 1) || j != mLen, runFlag);
 
-                    if (j != runLimit) {  // charAt(j) == TAB_CHAR
-                        h = mDir * nextTab(h * mDir);
+                        if (j != runLimit) {  // charAt(j) == TAB_CHAR
+                            h = mDir * nextTab(h * mDir);
+                        }
+                        segStart = j + 1;
                     }
-                    segStart = j + 1;
                 }
             }
+        } finally {
+            if (TRACE_TEXTLINE) {
+                Trace.endSection();
+            }
         }
     }
 
@@ -564,63 +576,76 @@
      */
     public float measure(@IntRange(from = 0) int offset, boolean trailing,
             @NonNull FontMetricsInt fmi, @Nullable RectF drawBounds, @Nullable LineInfo lineInfo) {
-        if (offset > mLen) {
-            throw new IndexOutOfBoundsException(
-                    "offset(" + offset + ") should be less than line limit(" + mLen + ")");
+        if (TRACE_TEXTLINE) {
+            Trace.beginSection("TextLine#measure");
         }
-        if (lineInfo != null) {
-            lineInfo.setClusterCount(0);
-        }
-        final int target = trailing ? offset - 1 : offset;
-        if (target < 0) {
-            return 0;
-        }
+        try {
+            if (offset > mLen) {
+                throw new IndexOutOfBoundsException(
+                        "offset(" + offset + ") should be less than line limit(" + mLen + ")");
+            }
+            if (lineInfo != null) {
+                lineInfo.setClusterCount(0);
+            }
+            final int target = trailing ? offset - 1 : offset;
+            if (target < 0) {
+                return 0;
+            }
 
-        float h = 0;
-        final int runCount = mDirections.getRunCount();
-        for (int runIndex = 0; runIndex < runCount; runIndex++) {
-            final int runStart = mDirections.getRunStart(runIndex);
-            if (runStart > mLen) break;
-            final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
-            final boolean runIsRtl = mDirections.isRunRtl(runIndex);
-            final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
+            float h = 0;
+            final int runCount = mDirections.getRunCount();
+            for (int runIndex = 0; runIndex < runCount; runIndex++) {
+                final int runStart = mDirections.getRunStart(runIndex);
+                if (runStart > mLen) break;
+                final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
+                final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+                final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
 
-            int segStart = runStart;
-            for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
-                if (j == runLimit || charAt(j) == TAB_CHAR) {
-                    final boolean targetIsInThisSegment = target >= segStart && target < j;
-                    final boolean sameDirection = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
+                int segStart = runStart;
+                for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
+                    if (j == runLimit || charAt(j) == TAB_CHAR) {
+                        final boolean targetIsInThisSegment = target >= segStart && target < j;
+                        final boolean sameDirection =
+                                (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
 
-                    if (targetIsInThisSegment && sameDirection) {
-                        return h + measureRun(segStart, offset, j, runIsRtl, fmi, drawBounds, null,
-                                0, h, lineInfo, runFlag);
-                    }
-
-                    final float segmentWidth = measureRun(segStart, j, j, runIsRtl, fmi, drawBounds,
-                            null, 0, h, lineInfo, runFlag);
-                    h += sameDirection ? segmentWidth : -segmentWidth;
-
-                    if (targetIsInThisSegment) {
-                        return h + measureRun(segStart, offset, j, runIsRtl, null, null,  null, 0,
-                                h, lineInfo, runFlag);
-                    }
-
-                    if (j != runLimit) {  // charAt(j) == TAB_CHAR
-                        if (offset == j) {
-                            return h;
+                        if (targetIsInThisSegment && sameDirection) {
+                            return h + measureRun(segStart, offset, j, runIsRtl, fmi, drawBounds,
+                                    null,
+                                    0, h, lineInfo, runFlag);
                         }
-                        h = mDir * nextTab(h * mDir);
-                        if (target == j) {
-                            return h;
-                        }
-                    }
 
-                    segStart = j + 1;
+                        final float segmentWidth = measureRun(segStart, j, j, runIsRtl, fmi,
+                                drawBounds,
+                                null, 0, h, lineInfo, runFlag);
+                        h += sameDirection ? segmentWidth : -segmentWidth;
+
+                        if (targetIsInThisSegment) {
+                            return h + measureRun(segStart, offset, j, runIsRtl, null, null, null,
+                                    0,
+                                    h, lineInfo, runFlag);
+                        }
+
+                        if (j != runLimit) {  // charAt(j) == TAB_CHAR
+                            if (offset == j) {
+                                return h;
+                            }
+                            h = mDir * nextTab(h * mDir);
+                            if (target == j) {
+                                return h;
+                            }
+                        }
+
+                        segStart = j + 1;
+                    }
                 }
             }
-        }
 
-        return h;
+            return h;
+        } finally {
+            if (TRACE_TEXTLINE) {
+                Trace.endSection();
+            }
+        }
     }
 
     /**
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index 821e13d..b300022 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -49,9 +49,9 @@
 
     /**
      * Tracks whether we have an outstanding request from the IME to show, but weren't able to
-     * execute it because we didn't have control yet.
+     * execute it because we didn't have control yet, or we didn't have a leash on the control yet.
      */
-    private boolean mIsRequestedVisibleAwaitingControl;
+    private boolean mIsRequestedVisibleAwaitingLeash;
 
     public ImeInsetsSourceConsumer(
             int id, InsetsState state, Supplier<Transaction> transactionSupplier,
@@ -68,7 +68,7 @@
         }
         final boolean insetsChanged = super.onAnimationStateChanged(running);
         if (!isShowRequested()) {
-            mIsRequestedVisibleAwaitingControl = false;
+            mIsRequestedVisibleAwaitingLeash = false;
             if (!running && !mHasPendingRequest) {
                 final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
                         ImeTracker.ORIGIN_CLIENT,
@@ -91,8 +91,8 @@
     public void onWindowFocusGained(boolean hasViewFocus) {
         super.onWindowFocusGained(hasViewFocus);
         getImm().registerImeConsumer(this);
-        if ((mController.getRequestedVisibleTypes() & getType()) != 0 && getControl() == null) {
-            mIsRequestedVisibleAwaitingControl = true;
+        if ((mController.getRequestedVisibleTypes() & getType()) != 0 && !hasLeash()) {
+            mIsRequestedVisibleAwaitingLeash = true;
         }
     }
 
@@ -100,7 +100,7 @@
     public void onWindowFocusLost() {
         super.onWindowFocusLost();
         getImm().unregisterImeConsumer(this);
-        mIsRequestedVisibleAwaitingControl = false;
+        mIsRequestedVisibleAwaitingLeash = false;
     }
 
     @Override
@@ -130,15 +130,15 @@
         ImeTracker.forLogging().onProgress(statsToken,
                 ImeTracker.PHASE_CLIENT_INSETS_CONSUMER_REQUEST_SHOW);
 
-        if (getControl() == null) {
-            // If control is null, schedule to show IME when control is available.
-            mIsRequestedVisibleAwaitingControl = true;
+        if (!hasLeash()) {
+            // If control or leash is null, schedule to show IME when both available.
+            mIsRequestedVisibleAwaitingLeash = true;
         }
         // If we had a request before to show from IME (tracked with mImeRequestedShow), reaching
         // this code here means that we now got control, so we can start the animation immediately.
         // If client window is trying to control IME and IME is already visible, it is immediate.
         if (fromIme
-                || (mState.isSourceOrDefaultVisible(getId(), getType()) && getControl() != null)) {
+                || (mState.isSourceOrDefaultVisible(getId(), getType()) && hasLeash())) {
             return ShowResult.SHOW_IMMEDIATELY;
         }
 
@@ -148,9 +148,9 @@
 
     void requestHide(boolean fromIme, @Nullable ImeTracker.Token statsToken) {
         if (!fromIme) {
-            // Create a new token to track the hide request when we have control,
+            // Create a new token to track the hide request when we have control and leash,
             // as we use the passed in token for the insets animation already.
-            final var notifyStatsToken = getControl() != null
+            final var notifyStatsToken = hasLeash()
                     ? ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
                         ImeTracker.ORIGIN_CLIENT,
                         SoftInputShowHideReason.HIDE_SOFT_INPUT_REQUEST_HIDE_WITH_CONTROL,
@@ -176,7 +176,7 @@
                 ImeTracker.PHASE_CLIENT_INSETS_CONSUMER_NOTIFY_HIDDEN);
 
         getImm().notifyImeHidden(mController.getHost().getWindowToken(), statsToken);
-        mIsRequestedVisibleAwaitingControl = false;
+        mIsRequestedVisibleAwaitingLeash = false;
         Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.hideRequestFromApi", 0);
     }
 
@@ -196,19 +196,28 @@
         if (!super.setControl(control, showTypes, hideTypes)) {
             return false;
         }
-        if (control == null && !mIsRequestedVisibleAwaitingControl) {
+        final boolean hasLeash = control != null && control.getLeash() != null;
+        if (!hasLeash && !mIsRequestedVisibleAwaitingLeash) {
             mController.setRequestedVisibleTypes(0 /* visibleTypes */, getType());
             removeSurface();
         }
-        if (control != null) {
-            mIsRequestedVisibleAwaitingControl = false;
+        if (hasLeash) {
+            mIsRequestedVisibleAwaitingLeash = false;
         }
         return true;
     }
 
     @Override
     protected boolean isRequestedVisibleAwaitingControl() {
-        return super.isRequestedVisibleAwaitingControl() || mIsRequestedVisibleAwaitingControl;
+        return super.isRequestedVisibleAwaitingControl() || mIsRequestedVisibleAwaitingLeash;
+    }
+
+    /**
+     * Checks whether the consumer has an insets source control with a leash.
+     */
+    private boolean hasLeash() {
+        final var control = getControl();
+        return control != null && control.getLeash() != null;
     }
 
     @Override
@@ -224,7 +233,7 @@
     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
         super.dumpDebug(proto, INSETS_SOURCE_CONSUMER);
-        proto.write(IS_REQUESTED_VISIBLE_AWAITING_CONTROL, mIsRequestedVisibleAwaitingControl);
+        proto.write(IS_REQUESTED_VISIBLE_AWAITING_CONTROL, mIsRequestedVisibleAwaitingLeash);
         proto.write(HAS_PENDING_REQUEST, mHasPendingRequest);
         proto.end(token);
     }
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..0a75f4e 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -18,6 +18,7 @@
 
 import static android.content.res.Resources.ID_NULL;
 import static android.os.Trace.TRACE_TAG_APP;
+import static android.os.Trace.TRACE_TAG_VIEW;
 import static android.service.autofill.Flags.FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION;
 import static android.view.ContentInfo.SOURCE_DRAG_AND_DROP;
 import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
@@ -2372,6 +2373,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 +2409,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 =
@@ -16335,20 +16357,20 @@
         ListenerInfo li = mListenerInfo;
         if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED) {
             try {
-                Trace.traceBegin(Trace.TRACE_TAG_VIEW, "View.onTouchListener#onTouch");
+                Trace.traceBegin(TRACE_TAG_VIEW, "View.onTouchListener#onTouch");
                 handled = li.mOnTouchListener.onTouch(this, event);
             } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+                Trace.traceEnd(TRACE_TAG_VIEW);
             }
         }
         if (handled) {
             return true;
         }
         try {
-            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "View#onTouchEvent");
+            Trace.traceBegin(TRACE_TAG_VIEW, "View#onTouchEvent");
             return onTouchEvent(event);
         } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            Trace.traceEnd(TRACE_TAG_VIEW);
         }
     }
 
@@ -23825,14 +23847,14 @@
     public void buildDrawingCache(boolean autoScale) {
         if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ?
                 mDrawingCache == null : mUnscaledDrawingCache == null)) {
-            if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-                Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+            if (Trace.isTagEnabled(TRACE_TAG_VIEW)) {
+                Trace.traceBegin(TRACE_TAG_VIEW,
                         "buildDrawingCache/SW Layer for " + getClass().getSimpleName());
             }
             try {
                 buildDrawingCacheImpl(autoScale);
             } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+                Trace.traceEnd(TRACE_TAG_VIEW);
             }
         }
     }
@@ -28959,6 +28981,7 @@
         surface.copyFrom(surfaceControl);
         IBinder token = null;
         try {
+            Trace.traceBegin(TRACE_TAG_VIEW, "startDragAndDrop#drawDragShadow");
             final Canvas canvas = isHardwareAccelerated()
                     ? surface.lockHardwareCanvas()
                     : surface.lockCanvas(null);
@@ -28967,33 +28990,40 @@
                 shadowBuilder.onDrawShadow(canvas);
             } finally {
                 surface.unlockCanvasAndPost(canvas);
+                Trace.traceEnd(TRACE_TAG_VIEW);
             }
 
-            token = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, flags, surfaceControl,
-                    root.getLastTouchSource(), root.getLastTouchDeviceId(),
-                    root.getLastTouchPointerId(), lastTouchPoint.x, lastTouchPoint.y,
-                    shadowTouchPoint.x, shadowTouchPoint.y, data);
-            if (ViewDebug.DEBUG_DRAG) {
-                Log.d(VIEW_LOG_TAG, "performDrag returned " + token);
+            Trace.traceBegin(TRACE_TAG_VIEW, "startDragAndDrop#performDrag");
+            try {
+                token = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, flags, surfaceControl,
+                        root.getLastTouchSource(), root.getLastTouchDeviceId(),
+                        root.getLastTouchPointerId(), lastTouchPoint.x, lastTouchPoint.y,
+                        shadowTouchPoint.x, shadowTouchPoint.y, data);
+                if (ViewDebug.DEBUG_DRAG) {
+                    Log.d(VIEW_LOG_TAG, "performDrag returned " + token);
+                }
+                if (token != null) {
+                    if (mAttachInfo.mDragSurface != null) {
+                        mAttachInfo.mDragSurface.release();
+                    }
+                    if (mAttachInfo.mDragData != null) {
+                        // Clean up previous drag data intents
+                        View.cleanUpPendingIntents(mAttachInfo.mDragData);
+                    }
+                    mAttachInfo.mDragSurface = surface;
+                    mAttachInfo.mDragToken = token;
+                    mAttachInfo.mDragData = data;
+                    // Cache the local state object for delivery with DragEvents
+                    root.setLocalDragState(myLocalState);
+                    if (a11yEnabled) {
+                        // Set for AccessibilityEvents
+                        mAttachInfo.mViewRootImpl.setDragStartedViewForAccessibility(this);
+                    }
+                }
+                return token != null;
+            } finally {
+                Trace.traceEnd(TRACE_TAG_VIEW);
             }
-            if (token != null) {
-                if (mAttachInfo.mDragSurface != null) {
-                    mAttachInfo.mDragSurface.release();
-                }
-                if (mAttachInfo.mDragData != null) {
-                    View.cleanUpPendingIntents(mAttachInfo.mDragData);
-                }
-                mAttachInfo.mDragSurface = surface;
-                mAttachInfo.mDragToken = token;
-                mAttachInfo.mDragData = data;
-                // Cache the local state object for delivery with DragEvents
-                root.setLocalDragState(myLocalState);
-                if (a11yEnabled) {
-                    // Set for AccessibilityEvents
-                    mAttachInfo.mViewRootImpl.setDragStartedViewForAccessibility(this);
-                }
-            }
-            return token != null;
         } catch (Exception e) {
             Log.e(VIEW_LOG_TAG, "Unable to initiate drag", e);
             return false;
@@ -33778,6 +33808,10 @@
                     return;
                 }
             }
+            if (sToolkitMetricsForFrameRateDecisionFlagValue) {
+                float sizePercentage = getSizePercentage();
+                viewRootImpl.recordViewPercentage(sizePercentage);
+            }
             int frameRateCategory;
             if (Float.isNaN(mPreferredFrameRate)) {
                 frameRateCategory = calculateFrameRateCategory(width, height);
@@ -33806,11 +33840,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 +33937,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..cae6672 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -21,8 +21,10 @@
 import static android.graphics.HardwareRenderer.SYNC_CONTEXT_IS_STOPPED;
 import static android.graphics.HardwareRenderer.SYNC_LOST_SURFACE_REWARD_IF_FOUND;
 import static android.os.IInputConstants.INVALID_INPUT_EVENT_ID;
+import static android.os.Trace.TRACE_TAG_VIEW;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
+import static android.view.DragEvent.ACTION_DRAG_LOCATION;
 import static android.view.InputDevice.SOURCE_CLASS_NONE;
 import static android.view.InsetsSource.ID_IME;
 import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
@@ -32,11 +34,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 +852,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 +4861,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) {
@@ -6464,7 +6475,18 @@
                     DragEvent event = (DragEvent) msg.obj;
                     // only present when this app called startDrag()
                     event.mLocalState = mLocalDragState;
-                    handleDragEvent(event);
+                    final boolean traceDragEvent = event.mAction != ACTION_DRAG_LOCATION;
+                    try {
+                        if (traceDragEvent) {
+                            Trace.traceBegin(TRACE_TAG_VIEW,
+                                    "c#" + DragEvent.actionToString(event.mAction));
+                        }
+                        handleDragEvent(event);
+                    } finally {
+                        if (traceDragEvent) {
+                            Trace.traceEnd(TRACE_TAG_VIEW);
+                        }
+                    }
                 } break;
                 case MSG_DISPATCH_SYSTEM_UI_VISIBILITY: {
                     handleDispatchSystemUiVisibilityChanged();
@@ -6540,6 +6562,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 +12391,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 +12435,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 +12483,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 +12523,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 +12534,7 @@
             mFrameRateCategoryLowCount = FRAME_RATE_CATEGORY_COUNT;
         }
 
+        int oldCategory = mPreferredFrameRateCategory;
         if (mFrameRateCategoryHighCount > 0) {
             mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_HIGH;
         } else if (mFrameRateCategoryHighHintCount > 0) {
@@ -12490,6 +12546,13 @@
         }
         mHasInvalidation = true;
         checkIdleness();
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)
+                && mPreferredFrameRateCategory != oldCategory
+                && mPreferredFrameRateCategory == frameRateCategory
+        ) {
+            mFrameRateCategoryChangeReason = reason;
+            mFrameRateCategoryView = view.getClass().getSimpleName();
+        }
     }
 
     /**
@@ -12617,12 +12680,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..107c5f2 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)
@@ -5870,7 +5871,10 @@
                     && height == WindowManager.LayoutParams.MATCH_PARENT;
         }
 
-        private static String layoutInDisplayCutoutModeToString(
+        /**
+         * @hide
+         */
+        public static String layoutInDisplayCutoutModeToString(
                 @LayoutInDisplayCutoutMode int mode) {
             switch (mode) {
                 case LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT:
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/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index e15baae..1d9eb71 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -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/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/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 3685bba..5227724 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -542,6 +542,9 @@
         // independent either.
         if (change.getMode() == TRANSIT_CHANGE) return false;
 
+        // Always fold the activity embedding change into the parent change.
+        if (change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) return false;
+
         TransitionInfo.Change parentChg = info.getChange(change.getParent());
         while (parentChg != null) {
             // If the parent is a visibility change, it will include the results of all child
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..a44e92c 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -156,11 +156,13 @@
     generate_product_characteristics_rro: true,
 
     flags_packages: [
+        "android.app.contextualsearch.flags-aconfig",
         "android.content.pm.flags-aconfig",
         "android.provider.flags-aconfig",
         "camera_platform_flags",
         "android.net.platform.flags-aconfig",
         "com.android.window.flags.window-aconfig",
+        "android.permission.flags-aconfig",
     ],
 }
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 8ea742d..1b9da20 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.
@@ -6776,12 +6776,13 @@
     <permission android:name="android.permission.MANAGE_BIOMETRIC_DIALOG"
                 android:protectionLevel="signature" />
 
-    <!-- Allows an application to set the BiometricDialog (SystemUI) logo .
+    <!-- Allows an application to set the advanced features on BiometricDialog (SystemUI), including
+         logo, logo description.
          <p>Not for use by third-party applications.
          @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt")
     -->
-    <permission android:name="android.permission.SET_BIOMETRIC_DIALOG_LOGO"
-                android:protectionLevel="signature" />
+    <permission android:name="android.permission.SET_BIOMETRIC_DIALOG_ADVANCED"
+                android:protectionLevel="signature|privileged" />
 
     <!-- Allows an application to control keyguard.  Only allowed for system processes.
         @hide -->
@@ -6920,7 +6921,8 @@
          projection. This is intended for on device screen recording system app.
          @FlaggedApi("android.permission.flags.sensitive_notification_app_protection") -->
     <permission android:name="android.permission.RECORD_SENSITIVE_CONTENT"
-                android:protectionLevel="signature"/>
+        android:protectionLevel="signature"
+        android:featureFlag="android.permission.flags.sensitive_notification_app_protection"/>
 
     <!-- @SystemApi Allows an application to read install sessions
          @hide This is not a third-party API (intended for system apps). -->
@@ -7207,6 +7209,13 @@
     <permission android:name="android.permission.ACCESS_SMARTSPACE"
         android:protectionLevel="signature|privileged|development" />
 
+    <!-- @SystemApi Allows an application to start a contextual search.
+         @FlaggedApi("android.app.contextualsearch.flags.enable_service")
+         @hide  <p>Not for use by third-party applications.</p> -->
+    <permission android:name="android.permission.ACCESS_CONTEXTUAL_SEARCH"
+        android:protectionLevel="signature|privileged"
+        android:featureFlag="android.app.contextualsearch.flags.enable_service"/>
+
     <!-- @SystemApi Allows an application to manage the wallpaper effects
      generation service.
      @hide  <p>Not for use by third-party applications.</p> -->
@@ -8149,6 +8158,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/attrs.xml b/core/res/res/values/attrs.xml
index 48cf09a..c77ee43 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -8988,6 +8988,14 @@
         <!-- Whether to show clock and other complications such as weather in the overlay. Default
              to true. Note that the overlay on dreams is currently only supported on tablets. -->
         <attr name="showClockAndComplications" format="boolean" />
+
+        <!-- Dream Category to determine the type of dream. Default to default.
+        @hide -->
+        <attr name="dreamCategory" format="integer">
+            <flag name="none" value="0x0" />
+            <flag name="low_light" value="0x1" />
+            <flag name="home_panel" value="0x2" />
+        </attr>
     </declare-styleable>
 
     <!--  Use <code>trust-agent</code> as the root tag of the XML resource that
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index b879c97..417c6df 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -238,204 +238,204 @@
 
     <color name="conversation_important_highlight">#F9AB00</color>
 
-    <!-- Lightest shade of the accent color used by the system. White.
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent1_0">#ffffff</color>
-    <!-- Shade of the accent system color at 99% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent1_10">#F9FCFF</color>
-    <!-- Shade of the accent system color at 95% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent1_50">#E0F3FF</color>
-    <!-- Shade of the accent system color at 90% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent1_100">#C1E8FF</color>
-    <!-- Shade of the accent system color at 80% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent1_200">#76D1FF</color>
-    <!-- Shade of the accent system color at 70% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent1_300">#4BB6E8</color>
-    <!-- Shade of the accent system color at 60% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent1_400">#219BCC</color>
-    <!-- Shade of the accent system color at 49.6% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent1_500">#007FAC</color>
-    <!-- Shade of the accent system color at 40% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent1_600">#00668B</color>
-    <!-- Shade of the accent system color at 30% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent1_700">#004C69</color>
-    <!-- Shade of the accent system color at 20% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent1_800">#003549</color>
-    <!-- Shade of the accent system color at 10% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent1_900">#001E2C</color>
-    <!-- Darkest shade of the accent color used by the system. Black.
-     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <!--Lightest shade of the Primary color used by the system. White.
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent1_0">#FFFFFF</color>
+    <!--Shade of the Primary system color at 99% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent1_10">#FEFBFF</color>
+    <!--Shade of the Primary system color at 95% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent1_50">#EEF0FF</color>
+    <!--Shade of the Primary system color at 90% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent1_100">#D9E2FF</color>
+    <!--Shade of the Primary system color at 80% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent1_200">#B0C6FF</color>
+    <!--Shade of the Primary system color at 70% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent1_300">#94AAE4</color>
+    <!--Shade of the Primary system color at 60% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent1_400">#7A90C8</color>
+    <!--Shade of the Primary system color at 50% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent1_500">#6076AC</color>
+    <!--Shade of the Primary system color at 40% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent1_600">#475D92</color>
+    <!--Shade of the Primary system color at 30% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent1_700">#2F4578</color>
+    <!--Shade of the Primary system color at 20% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent1_800">#152E60</color>
+    <!--Shade of the Primary system color at 10% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent1_900">#001945</color>
+    <!--Darkest shade of the Primary color used by the system. Black.
+     This value can be overlaid at runtime by OverlayManager RROs.-->
     <color name="system_accent1_1000">#000000</color>
 
-    <!-- Lightest shade of the secondary accent color used by the system. White.
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent2_0">#ffffff</color>
-    <!-- Shade of the secondary accent system color at 99% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent2_10">#F9FCFF</color>
-    <!-- Shade of the secondary accent system color at 95% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent2_50">#E0F3FF</color>
-    <!-- Shade of the secondary accent system color at 90% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent2_100">#D1E5F4</color>
-    <!-- Shade of the secondary accent system color at 80% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent2_200">#B5CAD7</color>
-    <!-- Shade of the secondary accent system color at 70% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent2_300">#9AAEBB</color>
-    <!-- Shade of the secondary accent system color at 60% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent2_400">#8094A0</color>
-    <!-- Shade of the secondary accent system color at 49.6% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent2_500">#657985</color>
-    <!-- Shade of the secondary accent system color at 40% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent2_600">#4E616C</color>
-    <!-- Shade of the secondary accent system color at 30% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent2_700">#374955</color>
-    <!-- Shade of the secondary accent system color at 20% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent2_800">#20333D</color>
-    <!-- Shade of the secondary accent system color at 10% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent2_900">#091E28</color>
-    <!-- Darkest shade of the secondary accent color used by the system. Black.
-     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <!--Lightest shade of the Secondary color used by the system. White.
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent2_0">#FFFFFF</color>
+    <!--Shade of the Secondary system color at 99% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent2_10">#FEFBFF</color>
+    <!--Shade of the Secondary system color at 95% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent2_50">#EEF0FF</color>
+    <!--Shade of the Secondary system color at 90% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent2_100">#DCE2F9</color>
+    <!--Shade of the Secondary system color at 80% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent2_200">#C0C6DC</color>
+    <!--Shade of the Secondary system color at 70% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent2_300">#A4ABC1</color>
+    <!--Shade of the Secondary system color at 60% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent2_400">#8A90A5</color>
+    <!--Shade of the Secondary system color at 50% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent2_500">#70778B</color>
+    <!--Shade of the Secondary system color at 40% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent2_600">#575E71</color>
+    <!--Shade of the Secondary system color at 30% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent2_700">#404659</color>
+    <!--Shade of the Secondary system color at 20% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent2_800">#2A3042</color>
+    <!--Shade of the Secondary system color at 10% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent2_900">#151B2C</color>
+    <!--Darkest shade of the Secondary color used by the system. Black.
+     This value can be overlaid at runtime by OverlayManager RROs.-->
     <color name="system_accent2_1000">#000000</color>
 
-    <!-- Lightest shade of the tertiary accent color used by the system. White.
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent3_0">#ffffff</color>
-    <!-- Shade of the tertiary accent system color at 99% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <!--Lightest shade of the Tertiary color used by the system. White.
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent3_0">#FFFFFF</color>
+    <!--Shade of the Tertiary system color at 99% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
     <color name="system_accent3_10">#FFFBFF</color>
-    <!-- Shade of the tertiary accent system color at 95% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent3_50">#F5EEFF</color>
-    <!-- Shade of the tertiary accent system color at 90% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent3_100">#E6DEFF</color>
-    <!-- Shade of the tertiary accent system color at 80% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent3_200">#CAC1EA</color>
-    <!-- Shade of the tertiary accent system color at 70% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent3_300">#AEA6CE</color>
-    <!-- Shade of the tertiary accent system color at 60% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent3_400">#938CB1</color>
-    <!-- Shade of the tertiary accent system color at 49% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent3_500">#787296</color>
-    <!-- Shade of the tertiary accent system color at 40% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent3_600">#605A7C</color>
-    <!-- Shade of the tertiary accent system color at 30% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent3_700">#484264</color>
-    <!-- Shade of the tertiary accent system color at 20% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent3_800">#322C4C</color>
-    <!-- Shade of the tertiary accent system color at 10% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent3_900">#1D1736</color>
-    <!-- Darkest shade of the tertiary accent color used by the system. Black.
-     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <!--Shade of the Tertiary system color at 95% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent3_50">#FFEBFA</color>
+    <!--Shade of the Tertiary system color at 90% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent3_100">#FDD7FA</color>
+    <!--Shade of the Tertiary system color at 80% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent3_200">#E0BBDD</color>
+    <!--Shade of the Tertiary system color at 70% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent3_300">#C3A0C1</color>
+    <!--Shade of the Tertiary system color at 60% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent3_400">#A886A6</color>
+    <!--Shade of the Tertiary system color at 50% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent3_500">#8C6D8C</color>
+    <!--Shade of the Tertiary system color at 40% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent3_600">#725572</color>
+    <!--Shade of the Tertiary system color at 30% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent3_700">#593D59</color>
+    <!--Shade of the Tertiary system color at 20% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent3_800">#412742</color>
+    <!--Shade of the Tertiary system color at 10% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_accent3_900">#2A122C</color>
+    <!--Darkest shade of the Tertiary color used by the system. Black.
+     This value can be overlaid at runtime by OverlayManager RROs.-->
     <color name="system_accent3_1000">#000000</color>
 
-    <!-- Lightest shade of the neutral color used by the system. White.
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral1_0">#ffffff</color>
-    <!-- Shade of the neutral system color at 99% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral1_10">#FCFCFF</color>
-    <!-- Shade of the neutral system color at 95% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral1_50">#F0F0F3</color>
-    <!-- Shade of the neutral system color at 90% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral1_100">#E1E3E5</color>
-    <!-- Shade of the neutral system color at 80% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral1_200">#C5C7C9</color>
-    <!-- Shade of the neutral system color at 70% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral1_300">#AAABAE</color>
-    <!-- Shade of the neutral system color at 60% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral1_400">#8F9193</color>
-    <!-- Shade of the neutral system color at 49% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral1_500">#747679</color>
-    <!-- Shade of the neutral system color at 40% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral1_600">#5C5F61</color>
-    <!-- Shade of the neutral system color at 30% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral1_700">#454749</color>
-    <!-- Shade of the neutral system color at 20% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral1_800">#2E3133</color>
-    <!-- Shade of the neutral system color at 10% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral1_900">#191C1E</color>
-    <!-- Darkest shade of the neutral color used by the system. Black.
-     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <!--Lightest shade of the Neutral color used by the system. White.
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_neutral1_0">#FFFFFF</color>
+    <!--Shade of the Neutral system color at 99% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_neutral1_10">#FEFBFF</color>
+    <!--Shade of the Neutral system color at 95% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_neutral1_50">#F1F0F7</color>
+    <!--Shade of the Neutral system color at 90% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_neutral1_100">#E2E2E9</color>
+    <!--Shade of the Neutral system color at 80% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_neutral1_200">#C6C6CD</color>
+    <!--Shade of the Neutral system color at 70% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_neutral1_300">#ABABB1</color>
+    <!--Shade of the Neutral system color at 60% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_neutral1_400">#909097</color>
+    <!--Shade of the Neutral system color at 50% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_neutral1_500">#76777D</color>
+    <!--Shade of the Neutral system color at 40% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_neutral1_600">#5D5E64</color>
+    <!--Shade of the Neutral system color at 30% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_neutral1_700">#45464C</color>
+    <!--Shade of the Neutral system color at 20% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_neutral1_800">#2F3036</color>
+    <!--Shade of the Neutral system color at 10% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_neutral1_900">#1A1B20</color>
+    <!--Darkest shade of the Neutral color used by the system. Black.
+     This value can be overlaid at runtime by OverlayManager RROs.-->
     <color name="system_neutral1_1000">#000000</color>
 
-    <!-- Lightest shade of the secondary neutral color used by the system. White.
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral2_0">#ffffff</color>
-    <!-- Shade of the secondary neutral system color at 99% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral2_10">#F9FCFF</color>
-    <!-- Shade of the secondary neutral system color at 95% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral2_50">#EBF1F8</color>
-    <!-- Shade of the secondary neutral system color at 90% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral2_100">#DCE3E9</color>
-    <!-- Shade of the secondary neutral system color at 80% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral2_200">#C0C7CD</color>
-    <!-- Shade of the secondary neutral system color at 70% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral2_300">#A5ACB2</color>
-    <!-- Shade of the secondary neutral system color at 60% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral2_400">#8A9297</color>
-    <!-- Shade of the secondary neutral system color at 49% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral2_500">#70777C</color>
-    <!-- Shade of the secondary neutral system color at 40% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral2_600">#585F65</color>
-    <!-- Shade of the secondary neutral system color at 30% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral2_700">#40484D</color>
-    <!-- Shade of the secondary neutral system color at 20% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral2_800">#2A3136</color>
-    <!-- Shade of the secondary neutral system color at 10% perceptual luminance (L* in L*a*b* color space).
-     This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral2_900">#161C20</color>
-    <!-- Darkest shade of the secondary neutral color used by the system. Black.
-     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <!--Lightest shade of the Secondary Neutral color used by the system. White.
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_neutral2_0">#FFFFFF</color>
+    <!--Shade of the Secondary Neutral system color at 99% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_neutral2_10">#FEFBFF</color>
+    <!--Shade of the Secondary Neutral system color at 95% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_neutral2_50">#F0F0FA</color>
+    <!--Shade of the Secondary Neutral system color at 90% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_neutral2_100">#E1E2EC</color>
+    <!--Shade of the Secondary Neutral system color at 80% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_neutral2_200">#C5C6D0</color>
+    <!--Shade of the Secondary Neutral system color at 70% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_neutral2_300">#A9ABB4</color>
+    <!--Shade of the Secondary Neutral system color at 60% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_neutral2_400">#8F9099</color>
+    <!--Shade of the Secondary Neutral system color at 50% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_neutral2_500">#757780</color>
+    <!--Shade of the Secondary Neutral system color at 40% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_neutral2_600">#5C5E67</color>
+    <!--Shade of the Secondary Neutral system color at 30% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_neutral2_700">#44464F</color>
+    <!--Shade of the Secondary Neutral system color at 20% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_neutral2_800">#2E3038</color>
+    <!--Shade of the Secondary Neutral system color at 10% perceptual luminance (L* in L*a*b* color space).
+     This value can be overlaid at runtime by OverlayManager RROs.-->
+    <color name="system_neutral2_900">#191B23</color>
+    <!--Darkest shade of the Secondary Neutral color used by the system. Black.
+     This value can be overlaid at runtime by OverlayManager RROs.-->
     <color name="system_neutral2_1000">#000000</color>
 
     <!-- Lightest shade of the error color used by the system. White.
@@ -480,106 +480,106 @@
 
     <!-- Colors used in Android system, from design system.
      These values can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_primary_container_light">#D8E2FF</color>
-    <color name="system_on_primary_container_light">#001A41</color>
-    <color name="system_primary_light">#445E91</color>
+    <color name="system_primary_container_light">#5E73A9</color>
+    <color name="system_on_primary_container_light">#FFFFFF</color>
+    <color name="system_primary_light">#2A4174</color>
     <color name="system_on_primary_light">#FFFFFF</color>
-    <color name="system_secondary_container_light">#DBE2F9</color>
-    <color name="system_on_secondary_container_light">#141B2C</color>
-    <color name="system_secondary_light">#575E71</color>
+    <color name="system_secondary_container_light">#6E7488</color>
+    <color name="system_on_secondary_container_light">#FFFFFF</color>
+    <color name="system_secondary_light">#3C4255</color>
     <color name="system_on_secondary_light">#FFFFFF</color>
-    <color name="system_tertiary_container_light">#FBD7FC</color>
-    <color name="system_on_tertiary_container_light">#29132D</color>
-    <color name="system_tertiary_light">#715573</color>
+    <color name="system_tertiary_container_light">#8A6A89</color>
+    <color name="system_on_tertiary_container_light">#FFFFFF</color>
+    <color name="system_tertiary_light">#553A55</color>
     <color name="system_on_tertiary_light">#FFFFFF</color>
-    <color name="system_background_light">#FAF9FD</color>
-    <color name="system_on_background_light">#1B1B1F</color>
-    <color name="system_surface_light">#FAF9FD</color>
-    <color name="system_on_surface_light">#1B1B1F</color>
-    <color name="system_surface_container_low_light">#F5F3F7</color>
+    <color name="system_background_light">#FAF8FF</color>
+    <color name="system_on_background_light">#1A1B20</color>
+    <color name="system_surface_light">#FAF8FF</color>
+    <color name="system_on_surface_light">#1A1B20</color>
+    <color name="system_surface_container_low_light">#F4F3FA</color>
     <color name="system_surface_container_lowest_light">#FFFFFF</color>
-    <color name="system_surface_container_light">#EFEDF1</color>
-    <color name="system_surface_container_high_light">#E9E7EC</color>
-    <color name="system_surface_container_highest_light">#E3E2E6</color>
-    <color name="system_surface_bright_light">#FAF9FD</color>
-    <color name="system_surface_dim_light">#DBD9DD</color>
+    <color name="system_surface_container_light">#EEEDF4</color>
+    <color name="system_surface_container_high_light">#E8E7EF</color>
+    <color name="system_surface_container_highest_light">#E2E2E9</color>
+    <color name="system_surface_bright_light">#FAF8FF</color>
+    <color name="system_surface_dim_light">#DAD9E0</color>
     <color name="system_surface_variant_light">#E1E2EC</color>
-    <color name="system_on_surface_variant_light">#44474F</color>
-    <color name="system_outline_light">#72747D</color>
-    <color name="system_outline_variant_light">#C4C7C5</color>
-    <color name="system_error_light">#C00003</color>
+    <color name="system_on_surface_variant_light">#40434B</color>
+    <color name="system_outline_light">#5D5F67</color>
+    <color name="system_outline_variant_light">#797A83</color>
+    <color name="system_error_light">#8C0009</color>
     <color name="system_on_error_light">#FFFFFF</color>
-    <color name="system_error_container_light">#FFDAD5</color>
-    <color name="system_on_error_container_light">#410000</color>
-    <color name="system_primary_fixed">#D8E2FF</color>
-    <color name="system_primary_fixed_dim">#ADC6FF</color>
-    <color name="system_on_primary_fixed">#001A41</color>
-    <color name="system_on_primary_fixed_variant">#2B4678</color>
-    <color name="system_secondary_fixed">#DBE2F9</color>
-    <color name="system_secondary_fixed_dim">#BFC6DC</color>
-    <color name="system_on_secondary_fixed">#141B2C</color>
-    <color name="system_on_secondary_fixed_variant">#3F4759</color>
-    <color name="system_tertiary_fixed">#FBD7FC</color>
-    <color name="system_tertiary_fixed_dim">#DEBCDF</color>
-    <color name="system_on_tertiary_fixed">#29132D</color>
-    <color name="system_on_tertiary_fixed_variant">#583E5B</color>
-    <color name="system_control_activated_light">#D8E2FF</color>
-    <color name="system_control_normal_light">#44474F</color>
-    <color name="system_control_highlight_light">#1F000000</color>
-    <color name="system_text_primary_inverse_light">#E3E2E6</color>
-    <color name="system_text_secondary_and_tertiary_inverse_light">#C4C6D0</color>
-    <color name="system_text_primary_inverse_disable_only_light">#E3E2E6</color>
-    <color name="system_text_secondary_and_tertiary_inverse_disabled_light">#E3E2E6</color>
-    <color name="system_text_hint_inverse_light">#E3E2E6</color>
-    <color name="system_palette_key_color_primary_light">#5D77AC</color>
-    <color name="system_palette_key_color_secondary_light">#6C7488</color>
-    <color name="system_palette_key_color_tertiary_light">#907292</color>
-    <color name="system_palette_key_color_neutral_light">#838387</color>
-    <color name="system_palette_key_color_neutral_variant_light">#777982</color>
-    <color name="system_primary_container_dark">#2B4678</color>
-    <color name="system_on_primary_container_dark">#D8E2FF</color>
-    <color name="system_primary_dark">#ADC6FF</color>
-    <color name="system_on_primary_dark">#102F60</color>
-    <color name="system_secondary_container_dark">#3F4759</color>
-    <color name="system_on_secondary_container_dark">#DBE2F9</color>
-    <color name="system_secondary_dark">#BFC6DC</color>
-    <color name="system_on_secondary_dark">#293041</color>
-    <color name="system_tertiary_container_dark">#583E5B</color>
-    <color name="system_on_tertiary_container_dark">#FBD7FC</color>
-    <color name="system_tertiary_dark">#DEBCDF</color>
-    <color name="system_on_tertiary_dark">#402843</color>
-    <color name="system_background_dark">#121316</color>
-    <color name="system_on_background_dark">#E3E2E6</color>
-    <color name="system_surface_dark">#121316</color>
-    <color name="system_on_surface_dark">#E3E2E6</color>
-    <color name="system_surface_container_low_dark">#1B1B1F</color>
-    <color name="system_surface_container_lowest_dark">#0D0E11</color>
-    <color name="system_surface_container_dark">#1F1F23</color>
-    <color name="system_surface_container_high_dark">#292A2D</color>
-    <color name="system_surface_container_highest_dark">#343538</color>
-    <color name="system_surface_bright_dark">#38393C</color>
-    <color name="system_surface_dim_dark">#121316</color>
-    <color name="system_surface_variant_dark">#44474F</color>
-    <color name="system_on_surface_variant_dark">#C4C6D0</color>
-    <color name="system_outline_dark">#8E9099</color>
-    <color name="system_outline_variant_dark">#444746</color>
-    <color name="system_error_dark">#FFB4A8</color>
-    <color name="system_on_error_dark">#690001</color>
-    <color name="system_error_container_dark">#930001</color>
-    <color name="system_on_error_container_dark">#FFDAD5</color>
-    <color name="system_control_activated_dark">#2B4678</color>
-    <color name="system_control_normal_dark">#C4C6D0</color>
-    <color name="system_control_highlight_dark">#33FFFFFF</color>
-    <color name="system_text_primary_inverse_dark">#1B1B1F</color>
-    <color name="system_text_secondary_and_tertiary_inverse_dark">#44474F</color>
-    <color name="system_text_primary_inverse_disable_only_dark">#1B1B1F</color>
-    <color name="system_text_secondary_and_tertiary_inverse_disabled_dark">#1B1B1F</color>
-    <color name="system_text_hint_inverse_dark">#1B1B1F</color>
-    <color name="system_palette_key_color_primary_dark">#5D77AC</color>
-    <color name="system_palette_key_color_secondary_dark">#6C7488</color>
-    <color name="system_palette_key_color_tertiary_dark">#907292</color>
-    <color name="system_palette_key_color_neutral_dark">#838387</color>
-    <color name="system_palette_key_color_neutral_variant_dark">#777982</color>
+    <color name="system_error_container_light">#DA342E</color>
+    <color name="system_on_error_container_light">#FFFFFF</color>
+    <color name="system_control_activated_light">#D9E2FF</color>
+    <color name="system_control_normal_light">#44464F</color>
+    <color name="system_control_highlight_light">#000000</color>
+    <color name="system_text_primary_inverse_light">#E2E2E9</color>
+    <color name="system_text_secondary_and_tertiary_inverse_light">#C5C6D0</color>
+    <color name="system_text_primary_inverse_disable_only_light">#E2E2E9</color>
+    <color name="system_text_secondary_and_tertiary_inverse_disabled_light">#E2E2E9</color>
+    <color name="system_text_hint_inverse_light">#E2E2E9</color>
+    <color name="system_palette_key_color_primary_light">#6076AC</color>
+    <color name="system_palette_key_color_secondary_light">#70778B</color>
+    <color name="system_palette_key_color_tertiary_light">#8C6D8C</color>
+    <color name="system_palette_key_color_neutral_light">#76777D</color>
+    <color name="system_palette_key_color_neutral_variant_light">#757780</color>
+    <color name="system_primary_container_dark">#7A90C8</color>
+    <color name="system_on_primary_container_dark">#000000</color>
+    <color name="system_primary_dark">#B7CAFF</color>
+    <color name="system_on_primary_dark">#00143B</color>
+    <color name="system_secondary_container_dark">#8A90A5</color>
+    <color name="system_on_secondary_container_dark">#000000</color>
+    <color name="system_secondary_dark">#C4CAE1</color>
+    <color name="system_on_secondary_dark">#0F1626</color>
+    <color name="system_tertiary_container_dark">#A886A6</color>
+    <color name="system_on_tertiary_container_dark">#000000</color>
+    <color name="system_tertiary_dark">#E4BFE2</color>
+    <color name="system_on_tertiary_dark">#240D26</color>
+    <color name="system_background_dark">#121318</color>
+    <color name="system_on_background_dark">#E2E2E9</color>
+    <color name="system_surface_dark">#121318</color>
+    <color name="system_on_surface_dark">#FCFAFF</color>
+    <color name="system_surface_container_low_dark">#1A1B20</color>
+    <color name="system_surface_container_lowest_dark">#0C0E13</color>
+    <color name="system_surface_container_dark">#1E1F25</color>
+    <color name="system_surface_container_high_dark">#282A2F</color>
+    <color name="system_surface_container_highest_dark">#33343A</color>
+    <color name="system_surface_bright_dark">#38393F</color>
+    <color name="system_surface_dim_dark">#121318</color>
+    <color name="system_surface_variant_dark">#44464F</color>
+    <color name="system_on_surface_variant_dark">#C9CAD4</color>
+    <color name="system_outline_dark">#A1A2AC</color>
+    <color name="system_outline_variant_dark">#81838C</color>
+    <color name="system_error_dark">#FFBAB1</color>
+    <color name="system_on_error_dark">#370001</color>
+    <color name="system_error_container_dark">#FF5449</color>
+    <color name="system_on_error_container_dark">#000000</color>
+    <color name="system_control_activated_dark">#2F4578</color>
+    <color name="system_control_normal_dark">#C5C6D0</color>
+    <color name="system_control_highlight_dark">#FFFFFF</color>
+    <color name="system_text_primary_inverse_dark">#1A1B20</color>
+    <color name="system_text_secondary_and_tertiary_inverse_dark">#44464F</color>
+    <color name="system_text_primary_inverse_disable_only_dark">#1A1B20</color>
+    <color name="system_text_secondary_and_tertiary_inverse_disabled_dark">#1A1B20</color>
+    <color name="system_text_hint_inverse_dark">#1A1B20</color>
+    <color name="system_palette_key_color_primary_dark">#6076AC</color>
+    <color name="system_palette_key_color_secondary_dark">#70778B</color>
+    <color name="system_palette_key_color_tertiary_dark">#8C6D8C</color>
+    <color name="system_palette_key_color_neutral_dark">#76777D</color>
+    <color name="system_palette_key_color_neutral_variant_dark">#757780</color>
+    <color name="system_primary_fixed">#5E73A9</color>
+    <color name="system_primary_fixed_dim">#455B8F</color>
+    <color name="system_on_primary_fixed">#FFFFFF</color>
+    <color name="system_on_primary_fixed_variant">#FFFFFF</color>
+    <color name="system_secondary_fixed">#6E7488</color>
+    <color name="system_secondary_fixed_dim">#555C6F</color>
+    <color name="system_on_secondary_fixed">#FFFFFF</color>
+    <color name="system_on_secondary_fixed_variant">#FFFFFF</color>
+    <color name="system_tertiary_fixed">#8A6A89</color>
+    <color name="system_tertiary_fixed_dim">#705270</color>
+    <color name="system_on_tertiary_fixed">#FFFFFF</color>
+    <color name="system_on_tertiary_fixed_variant">#FFFFFF</color>
 
     <!-- Accessibility shortcut icon background color -->
     <color name="accessibility_feature_background">#5F6368</color> <!-- Google grey 700 -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index f59c099..e3f1cb6 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
@@ -6985,4 +6977,8 @@
 
     <!-- The key containing the branching boolean for legacy Search. -->
     <string name="config_defaultContextualSearchLegacyEnabled" translatable="false" />
+
+    <!-- Whether WM DisplayContent supports high performance transitions
+         (lower-end devices may want to disable) -->
+    <bool name="config_deviceSupportsHighPerfTransitions">true</bool>
 </resources>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 3303c07..4be8c1b 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -167,6 +167,8 @@
     <public name="shiftDrawingOffsetForStartOverhang" />
     <!-- @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") -->
     <public name="windowIsFrameRatePowerSavingsBalanced"/>
+    <!-- @FlaggedApi("android.service.controls.flags.Flags.FLAG_HOME_PANEL_DREAM") -->
+    <public name="dreamCategory"/>
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01bc0000">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c603fa7..9628d30 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" />
@@ -5376,4 +5374,7 @@
   <java-symbol type="string" name="config_defaultContextualSearchLegacyEnabled" />
 
   <java-symbol type="string" name="unarchival_session_app_label" />
+
+  <!-- Whether WM DisplayContent supports high performance transitions -->
+  <java-symbol type="bool" name="config_deviceSupportsHighPerfTransitions" />
 </resources>
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..47b28899 100644
--- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -28,6 +28,7 @@
 import static org.mockito.AdditionalMatchers.not;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.notNull;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
@@ -120,6 +121,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 +132,55 @@
             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(false) /* fromIme */,
+                    and(not(eq(statsToken)), notNull()));
+            verify(mController, never()).applyAnimation(
+                    eq(WindowInsets.Type.ime()), eq(false) /* show */, eq(false) /* fromIme */,
+                    and(not(eq(statsToken)), notNull()));
+        });
+    }
+
+    @Test
+    public void testImeRequestedVisibleAwaitingLeash() {
+        // Set null control, then request show.
+        mController.onControlsChanged(new InsetsSourceControl[] { null });
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            // Request IME visible before control is available.
+            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));
+            // Clear previous invocations to verify this is never called with control without leash.
+            clearInvocations(mController);
+
+            // set control without leash and verify visibility is not applied.
+            InsetsSourceControl control = new InsetsSourceControl(ID_IME,
+                    WindowInsets.Type.ime(), null /* leash */, false, new Point(), Insets.NONE);
+            mController.onControlsChanged(new InsetsSourceControl[] { control });
+            // IME show animation should not be triggered when control becomes available,
+            // as we have no leash.
             verify(mController, never()).applyAnimation(
-                    eq(WindowInsets.Type.ime()), eq(false) /* 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(false) /* fromIme */,
+                    and(not(eq(statsToken)), notNull()));
+
+            // set control with leash and verify visibility is applied.
+            InsetsSourceControl controlWithLeash = new InsetsSourceControl(ID_IME,
+                    WindowInsets.Type.ime(), mLeash, false, new Point(), Insets.NONE);
+            mController.onControlsChanged(new InsetsSourceControl[] { controlWithLeash });
+            // IME show animation should be triggered when control with leash becomes available.
+            verify(mController).applyAnimation(
+                    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(false) /* fromIme */,
+                    and(not(eq(statsToken)), notNull()));
         });
     }
 
@@ -159,6 +208,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 +237,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/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/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 749f0e1..9c1c7006 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -454,7 +454,7 @@
         <!-- Permissions required for CTS test - android.server.biometrics -->
         <permission name="android.permission.USE_BIOMETRIC" />
         <permission name="android.permission.TEST_BIOMETRIC" />
-        <permission name="android.permission.SET_BIOMETRIC_DIALOG_LOGO" />
+        <permission name="android.permission.SET_BIOMETRIC_DIALOG_ADVANCED" />
         <permission name="android.permission.MANAGE_BIOMETRIC_DIALOG" />
         <!-- Permissions required for CTS test - CtsContactsProviderTestCases -->
         <permission name="android.contacts.permission.MANAGE_SIM_ACCOUNTS" />
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/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
new file mode 100644
index 0000000..100185b
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
@@ -0,0 +1,159 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import static android.util.TypedValue.COMPLEX_UNIT_DIP;
+
+import static androidx.window.extensions.embedding.DividerAttributes.RATIO_UNSET;
+import static androidx.window.extensions.embedding.DividerAttributes.WIDTH_UNSET;
+import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_BOTTOM;
+import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_LEFT;
+import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_RIGHT;
+import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_TOP;
+
+import android.annotation.Nullable;
+import android.app.ActivityThread;
+import android.content.Context;
+import android.util.TypedValue;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
+
+/**
+ * Manages the rendering and interaction of the divider.
+ */
+class DividerPresenter {
+    // TODO(b/327067596) Update based on UX guidance.
+    @VisibleForTesting static final float DEFAULT_MIN_RATIO = 0.35f;
+    @VisibleForTesting static final float DEFAULT_MAX_RATIO = 0.65f;
+    @VisibleForTesting static final int DEFAULT_DIVIDER_WIDTH_DP = 24;
+
+    static int getDividerWidthPx(@NonNull DividerAttributes dividerAttributes) {
+        int dividerWidthDp = dividerAttributes.getWidthDp();
+
+        // TODO(b/329193115) support divider on secondary display
+        final Context applicationContext = ActivityThread.currentActivityThread().getApplication();
+
+        return (int) TypedValue.applyDimension(
+                COMPLEX_UNIT_DIP,
+                dividerWidthDp,
+                applicationContext.getResources().getDisplayMetrics());
+    }
+
+    /**
+     * Returns the container bound offset that is a result of the presence of a divider.
+     *
+     * The offset is the relative position change for the container edge that is next to the divider
+     * due to the presence of the divider. The value could be negative or positive depending on the
+     * container position. Positive values indicate that the edge is shifting towards the right
+     * (or bottom) and negative values indicate that the edge is shifting towards the left (or top).
+     *
+     * @param splitAttributes the {@link SplitAttributes} of the split container that we want to
+     *                        compute bounds offset.
+     * @param position        the position of the container in the split that we want to compute
+     *                        bounds offset for.
+     * @return the bounds offset in pixels.
+     */
+    static int getBoundsOffsetForDivider(
+            @NonNull SplitAttributes splitAttributes,
+            @SplitPresenter.ContainerPosition int position) {
+        if (!Flags.activityEmbeddingInteractiveDividerFlag()) {
+            return 0;
+        }
+        final DividerAttributes dividerAttributes = splitAttributes.getDividerAttributes();
+        if (dividerAttributes == null) {
+            return 0;
+        }
+        final int dividerWidthPx = getDividerWidthPx(dividerAttributes);
+        return getBoundsOffsetForDivider(
+                dividerWidthPx,
+                splitAttributes.getSplitType(),
+                position);
+    }
+
+    @VisibleForTesting
+    static int getBoundsOffsetForDivider(
+            int dividerWidthPx,
+            @NonNull SplitAttributes.SplitType splitType,
+            @SplitPresenter.ContainerPosition int position) {
+        if (splitType instanceof SplitAttributes.SplitType.ExpandContainersSplitType) {
+            // No divider is needed for the ExpandContainersSplitType.
+            return 0;
+        }
+        int primaryOffset;
+        if (splitType instanceof final SplitAttributes.SplitType.RatioSplitType splitRatio) {
+            // When a divider is present, both containers shrink by an amount proportional to their
+            // split ratio and sum to the width of the divider, so that the ending sizing of the
+            // containers still maintain the same ratio.
+            primaryOffset = (int) (dividerWidthPx * splitRatio.getRatio());
+        } else {
+            // Hinge split type (and other future split types) will have the divider width equally
+            // distributed to both containers.
+            primaryOffset = dividerWidthPx / 2;
+        }
+        final int secondaryOffset = dividerWidthPx - primaryOffset;
+        switch (position) {
+            case CONTAINER_POSITION_LEFT:
+            case CONTAINER_POSITION_TOP:
+                return -primaryOffset;
+            case CONTAINER_POSITION_RIGHT:
+            case CONTAINER_POSITION_BOTTOM:
+                return secondaryOffset;
+            default:
+                throw new IllegalArgumentException("Unknown position:" + position);
+        }
+    }
+
+    /**
+     * Sanitizes and sets default values in the {@link DividerAttributes}.
+     *
+     * Unset values will be set with system default values. See
+     * {@link DividerAttributes#WIDTH_UNSET} and {@link DividerAttributes#RATIO_UNSET}.
+     *
+     * @param dividerAttributes input {@link DividerAttributes}
+     * @return a {@link DividerAttributes} that has all values properly set.
+     */
+    @Nullable
+    static DividerAttributes sanitizeDividerAttributes(
+            @Nullable DividerAttributes dividerAttributes) {
+        if (dividerAttributes == null) {
+            return null;
+        }
+        int widthDp = dividerAttributes.getWidthDp();
+        if (widthDp == WIDTH_UNSET) {
+            widthDp = DEFAULT_DIVIDER_WIDTH_DP;
+        }
+
+        float minRatio = dividerAttributes.getPrimaryMinRatio();
+        if (minRatio == RATIO_UNSET) {
+            minRatio = DEFAULT_MIN_RATIO;
+        }
+
+        float maxRatio = dividerAttributes.getPrimaryMaxRatio();
+        if (maxRatio == RATIO_UNSET) {
+            maxRatio = DEFAULT_MAX_RATIO;
+        }
+
+        return new DividerAttributes.Builder(dividerAttributes)
+                .setWidthDp(widthDp)
+                .setPrimaryMinRatio(minRatio)
+                .setPrimaryMaxRatio(maxRatio)
+                .build();
+    }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index b53b9c5..f680694 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -18,6 +18,7 @@
 
 import static android.content.pm.PackageManager.MATCH_ALL;
 
+import static androidx.window.extensions.embedding.DividerPresenter.getBoundsOffsetForDivider;
 import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_TASK;
 
 import android.app.Activity;
@@ -85,10 +86,10 @@
     })
     private @interface Position {}
 
-    private static final int CONTAINER_POSITION_LEFT = 0;
-    private static final int CONTAINER_POSITION_TOP = 1;
-    private static final int CONTAINER_POSITION_RIGHT = 2;
-    private static final int CONTAINER_POSITION_BOTTOM = 3;
+    static final int CONTAINER_POSITION_LEFT = 0;
+    static final int CONTAINER_POSITION_TOP = 1;
+    static final int CONTAINER_POSITION_RIGHT = 2;
+    static final int CONTAINER_POSITION_BOTTOM = 3;
 
     @IntDef(value = {
             CONTAINER_POSITION_LEFT,
@@ -96,7 +97,7 @@
             CONTAINER_POSITION_RIGHT,
             CONTAINER_POSITION_BOTTOM,
     })
-    private @interface ContainerPosition {}
+    @interface ContainerPosition {}
 
     /**
      * Result of {@link #expandSplitContainerIfNeeded(WindowContainerTransaction, SplitContainer,
@@ -738,6 +739,15 @@
     private SplitAttributes sanitizeSplitAttributes(@NonNull TaskProperties taskProperties,
             @NonNull SplitAttributes splitAttributes,
             @Nullable Pair<Size, Size> minDimensionsPair) {
+        // Sanitize the DividerAttributes and set default values.
+        if (splitAttributes.getDividerAttributes() != null) {
+            splitAttributes = new SplitAttributes.Builder(splitAttributes)
+                    .setDividerAttributes(
+                            DividerPresenter.sanitizeDividerAttributes(
+                                    splitAttributes.getDividerAttributes())
+                    ).build();
+        }
+
         if (minDimensionsPair == null) {
             return splitAttributes;
         }
@@ -930,18 +940,18 @@
      */
     private static SplitAttributes updateSplitAttributesType(
             @NonNull SplitAttributes splitAttributes, @NonNull SplitType splitTypeToUpdate) {
-        return new SplitAttributes.Builder()
+        return new SplitAttributes.Builder(splitAttributes)
                 .setSplitType(splitTypeToUpdate)
-                .setLayoutDirection(splitAttributes.getLayoutDirection())
-                .setAnimationBackground(splitAttributes.getAnimationBackground())
                 .build();
     }
 
     @NonNull
     private Rect getLeftContainerBounds(@NonNull Configuration taskConfiguration,
             @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
+        final int dividerOffset = getBoundsOffsetForDivider(
+                splitAttributes, CONTAINER_POSITION_LEFT);
         final int right = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes,
-                CONTAINER_POSITION_LEFT, foldingFeature);
+                CONTAINER_POSITION_LEFT, foldingFeature) + dividerOffset;
         final Rect taskBounds = taskConfiguration.windowConfiguration.getBounds();
         return new Rect(taskBounds.left, taskBounds.top, right, taskBounds.bottom);
     }
@@ -949,8 +959,10 @@
     @NonNull
     private Rect getRightContainerBounds(@NonNull Configuration taskConfiguration,
             @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
+        final int dividerOffset = getBoundsOffsetForDivider(
+                splitAttributes, CONTAINER_POSITION_RIGHT);
         final int left = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes,
-                CONTAINER_POSITION_RIGHT, foldingFeature);
+                CONTAINER_POSITION_RIGHT, foldingFeature) + dividerOffset;
         final Rect parentBounds = taskConfiguration.windowConfiguration.getBounds();
         return new Rect(left, parentBounds.top, parentBounds.right, parentBounds.bottom);
     }
@@ -958,8 +970,10 @@
     @NonNull
     private Rect getTopContainerBounds(@NonNull Configuration taskConfiguration,
             @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
+        final int dividerOffset = getBoundsOffsetForDivider(
+                splitAttributes, CONTAINER_POSITION_TOP);
         final int bottom = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes,
-                CONTAINER_POSITION_TOP, foldingFeature);
+                CONTAINER_POSITION_TOP, foldingFeature) + dividerOffset;
         final Rect parentBounds = taskConfiguration.windowConfiguration.getBounds();
         return new Rect(parentBounds.left, parentBounds.top, parentBounds.right, bottom);
     }
@@ -967,8 +981,10 @@
     @NonNull
     private Rect getBottomContainerBounds(@NonNull Configuration taskConfiguration,
             @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
+        final int dividerOffset = getBoundsOffsetForDivider(
+                splitAttributes, CONTAINER_POSITION_BOTTOM);
         final int top = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes,
-                CONTAINER_POSITION_BOTTOM, foldingFeature);
+                CONTAINER_POSITION_BOTTOM, foldingFeature) + dividerOffset;
         final Rect parentBounds = taskConfiguration.windowConfiguration.getBounds();
         return new Rect(parentBounds.left, top, parentBounds.right, parentBounds.bottom);
     }
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
new file mode 100644
index 0000000..2a277f4
--- /dev/null
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import static androidx.window.extensions.embedding.DividerPresenter.getBoundsOffsetForDivider;
+import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_BOTTOM;
+import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_LEFT;
+import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_RIGHT;
+import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_TOP;
+
+import static org.junit.Assert.assertEquals;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test class for {@link DividerPresenter}.
+ *
+ * Build/Install/Run:
+ *  atest WMJetpackUnitTests:DividerPresenterTest
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DividerPresenterTest {
+    @Test
+    public void testSanitizeDividerAttributes_setDefaultValues() {
+        DividerAttributes attributes =
+                new DividerAttributes.Builder(DividerAttributes.DIVIDER_TYPE_DRAGGABLE).build();
+        DividerAttributes sanitized = DividerPresenter.sanitizeDividerAttributes(attributes);
+
+        assertEquals(DividerAttributes.DIVIDER_TYPE_DRAGGABLE, sanitized.getDividerType());
+        assertEquals(DividerPresenter.DEFAULT_DIVIDER_WIDTH_DP, sanitized.getWidthDp());
+        assertEquals(DividerPresenter.DEFAULT_MIN_RATIO, sanitized.getPrimaryMinRatio(),
+                0.0f /* delta */);
+        assertEquals(DividerPresenter.DEFAULT_MAX_RATIO, sanitized.getPrimaryMaxRatio(),
+                0.0f /* delta */);
+    }
+
+    @Test
+    public void testSanitizeDividerAttributes_notChangingValidValues() {
+        DividerAttributes attributes =
+                new DividerAttributes.Builder(DividerAttributes.DIVIDER_TYPE_DRAGGABLE)
+                        .setWidthDp(10)
+                        .setPrimaryMinRatio(0.3f)
+                        .setPrimaryMaxRatio(0.7f)
+                        .build();
+        DividerAttributes sanitized = DividerPresenter.sanitizeDividerAttributes(attributes);
+
+        assertEquals(attributes, sanitized);
+    }
+
+    @Test
+    public void testGetBoundsOffsetForDivider_ratioSplitType() {
+        final int dividerWidthPx = 100;
+        final float splitRatio = 0.25f;
+        final SplitAttributes.SplitType splitType =
+                new SplitAttributes.SplitType.RatioSplitType(splitRatio);
+        final int expectedTopLeftOffset = 25;
+        final int expectedBottomRightOffset = 75;
+
+        assertDividerOffsetEquals(
+                dividerWidthPx, splitType, expectedTopLeftOffset, expectedBottomRightOffset);
+    }
+
+    @Test
+    public void testGetBoundsOffsetForDivider_ratioSplitType_withRounding() {
+        final int dividerWidthPx = 101;
+        final float splitRatio = 0.25f;
+        final SplitAttributes.SplitType splitType =
+                new SplitAttributes.SplitType.RatioSplitType(splitRatio);
+        final int expectedTopLeftOffset = 25;
+        final int expectedBottomRightOffset = 76;
+
+        assertDividerOffsetEquals(
+                dividerWidthPx, splitType, expectedTopLeftOffset, expectedBottomRightOffset);
+    }
+
+    @Test
+    public void testGetBoundsOffsetForDivider_hingeSplitType() {
+        final int dividerWidthPx = 100;
+        final SplitAttributes.SplitType splitType =
+                new SplitAttributes.SplitType.HingeSplitType(
+                        new SplitAttributes.SplitType.RatioSplitType(0.5f));
+
+        final int expectedTopLeftOffset = 50;
+        final int expectedBottomRightOffset = 50;
+
+        assertDividerOffsetEquals(
+                dividerWidthPx, splitType, expectedTopLeftOffset, expectedBottomRightOffset);
+    }
+
+    @Test
+    public void testGetBoundsOffsetForDivider_expandContainersSplitType() {
+        final int dividerWidthPx = 100;
+        final SplitAttributes.SplitType splitType =
+                new SplitAttributes.SplitType.ExpandContainersSplitType();
+        // Always return 0 for ExpandContainersSplitType as divider is not needed.
+        final int expectedTopLeftOffset = 0;
+        final int expectedBottomRightOffset = 0;
+
+        assertDividerOffsetEquals(
+                dividerWidthPx, splitType, expectedTopLeftOffset, expectedBottomRightOffset);
+    }
+
+    private void assertDividerOffsetEquals(
+            int dividerWidthPx,
+            @NonNull SplitAttributes.SplitType splitType,
+            int expectedTopLeftOffset,
+            int expectedBottomRightOffset) {
+        int offset = getBoundsOffsetForDivider(
+                dividerWidthPx,
+                splitType,
+                CONTAINER_POSITION_LEFT
+        );
+        assertEquals(-expectedTopLeftOffset, offset);
+
+        offset = getBoundsOffsetForDivider(
+                dividerWidthPx,
+                splitType,
+                CONTAINER_POSITION_RIGHT
+        );
+        assertEquals(expectedBottomRightOffset, offset);
+
+        offset = getBoundsOffsetForDivider(
+                dividerWidthPx,
+                splitType,
+                CONTAINER_POSITION_TOP
+        );
+        assertEquals(-expectedTopLeftOffset, offset);
+
+        offset = getBoundsOffsetForDivider(
+                dividerWidthPx,
+                splitType,
+                CONTAINER_POSITION_BOTTOM
+        );
+        assertEquals(expectedBottomRightOffset, offset);
+    }
+}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index bdeeb73..cdb37ac 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -294,7 +294,10 @@
         doReturn(tf).when(splitContainer).getPrimaryContainer();
         doReturn(tf).when(splitContainer).getSecondaryContainer();
         doReturn(createTestTaskContainer()).when(splitContainer).getTaskContainer();
-        doReturn(createSplitRule(mActivity, mActivity)).when(splitContainer).getSplitRule();
+        final SplitRule splitRule = createSplitRule(mActivity, mActivity);
+        doReturn(splitRule).when(splitContainer).getSplitRule();
+        doReturn(splitRule.getDefaultSplitAttributes())
+                .when(splitContainer).getDefaultSplitAttributes();
         taskContainer = mSplitController.getTaskContainer(TASK_ID);
         taskContainer.addSplitContainer(splitContainer);
         // Add a mock SplitContainer on top of splitContainer
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/BubbleFlyoutView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java
index 6a5f785..42de401 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java
@@ -24,6 +24,7 @@
 
 import android.animation.ArgbEvaluator;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
@@ -74,7 +75,7 @@
 
     private final int mFlyoutElevation;
     private final int mBubbleElevation;
-    private final int mFloatingBackgroundColor;
+    private int mFloatingBackgroundColor;
     private final float mCornerRadius;
 
     private final ViewGroup mFlyoutTextContainer;
@@ -107,6 +108,9 @@
     /** Color of the 'new' dot that the flyout will transform into. */
     private int mDotColor;
 
+    /** Keeps last used night mode flags **/
+    private int mNightModeFlags;
+
     /** The outline of the triangle, used for elevation shadows. */
     private final Outline mTriangleOutline = new Outline();
 
@@ -176,11 +180,8 @@
         mFlyoutElevation = res.getDimensionPixelSize(R.dimen.bubble_flyout_elevation);
 
         final TypedArray ta = mContext.obtainStyledAttributes(
-                new int[] {
-                        com.android.internal.R.attr.materialColorSurfaceContainer,
-                        android.R.attr.dialogCornerRadius});
-        mFloatingBackgroundColor = ta.getColor(0, Color.WHITE);
-        mCornerRadius = ta.getDimensionPixelSize(1, 0);
+                new int[] {android.R.attr.dialogCornerRadius});
+        mCornerRadius = ta.getDimensionPixelSize(0, 0);
         ta.recycle();
 
         // Add padding for the pointer on either side, onDraw will draw it in this space.
@@ -198,19 +199,17 @@
         // Use locale direction so the text is aligned correctly.
         setLayoutDirection(LAYOUT_DIRECTION_LOCALE);
 
-        mBgPaint.setColor(mFloatingBackgroundColor);
-
         mLeftTriangleShape =
                 new ShapeDrawable(TriangleShape.createHorizontal(
                         mPointerSize, mPointerSize, true /* isPointingLeft */));
         mLeftTriangleShape.setBounds(0, 0, mPointerSize, mPointerSize);
-        mLeftTriangleShape.getPaint().setColor(mFloatingBackgroundColor);
 
         mRightTriangleShape =
                 new ShapeDrawable(TriangleShape.createHorizontal(
                         mPointerSize, mPointerSize, false /* isPointingLeft */));
         mRightTriangleShape.setBounds(0, 0, mPointerSize, mPointerSize);
-        mRightTriangleShape.getPaint().setColor(mFloatingBackgroundColor);
+
+        applyConfigurationColors(getResources().getConfiguration());
     }
 
     @Override
@@ -244,6 +243,13 @@
         fade(false /* in */, stackPos, hideDot, afterFadeOut);
     }
 
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        if (applyColorsAccordingToConfiguration(newConfig)) {
+            invalidate();
+        }
+    }
+
     /*
      * Fade-out above or fade-in from below.
      */
@@ -424,6 +430,42 @@
     }
 
     /**
+     * Resolving and applying colors according to the ui mode, remembering most recent mode.
+     *
+     * @return {@code true} if night mode setting has changed since the last invocation,
+     * {@code false} otherwise
+     */
+    boolean applyColorsAccordingToConfiguration(Configuration configuration) {
+        int nightModeFlags = configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK;
+        boolean flagsChanged = nightModeFlags != mNightModeFlags;
+        if (flagsChanged) {
+            mNightModeFlags = nightModeFlags;
+            applyConfigurationColors(configuration);
+        }
+        return flagsChanged;
+    }
+
+    private void applyConfigurationColors(Configuration configuration) {
+        int nightModeFlags = configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK;
+        boolean isNightModeOn = nightModeFlags == Configuration.UI_MODE_NIGHT_YES;
+        try (TypedArray ta = mContext.obtainStyledAttributes(
+                new int[]{
+                        com.android.internal.R.attr.materialColorSurfaceContainer,
+                        com.android.internal.R.attr.materialColorOnSurface,
+                        com.android.internal.R.attr.materialColorOnSurfaceVariant})) {
+            mFloatingBackgroundColor = ta.getColor(0,
+                    isNightModeOn ? Color.BLACK : Color.WHITE);
+            mSenderText.setTextColor(ta.getColor(1,
+                    isNightModeOn ? Color.WHITE : Color.BLACK));
+            mMessageText.setTextColor(ta.getColor(2,
+                    isNightModeOn ? Color.WHITE : Color.BLACK));
+            mBgPaint.setColor(mFloatingBackgroundColor);
+            mLeftTriangleShape.getPaint().setColor(mFloatingBackgroundColor);
+            mRightTriangleShape.getPaint().setColor(mFloatingBackgroundColor);
+        }
+    }
+
+    /**
      * Renders the background, which is either the rounded 'chat bubble' flyout, or some state
      * between that and the 'new' dot over the bubbles.
      */
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/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
new file mode 100644
index 0000000..6781d08
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:JvmName("AppCompatUtils")
+
+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/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 4a3130f..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
@@ -81,6 +84,8 @@
 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
@@ -164,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() },
@@ -269,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 -> {
@@ -310,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(
@@ -446,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()
@@ -492,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
@@ -515,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")
@@ -590,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 {
@@ -737,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
                 }
@@ -774,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)
@@ -821,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
@@ -901,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
+            )
         }
     }
 
@@ -1111,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/draganddrop/GlobalDragListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt
index 8826141..31214eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt
@@ -17,6 +17,8 @@
 
 import android.app.ActivityManager
 import android.os.RemoteException
+import android.os.Trace
+import android.os.Trace.TRACE_TAG_WINDOW_MANAGER
 import android.util.Log
 import android.view.DragEvent
 import android.view.IWindowManager
@@ -27,6 +29,7 @@
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.protolog.ShellProtoLogGroup
 import java.util.function.Consumer
+import kotlin.random.Random
 
 /**
  * Manages the listener and callbacks for unhandled global drags.
@@ -101,10 +104,15 @@
 
     @VisibleForTesting
     fun onUnhandledDrop(dragEvent: DragEvent, wmCallback: IUnhandledDragCallback) {
+        val traceCookie = Random.nextInt()
+        Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "GlobalDragListener.onUnhandledDrop",
+            traceCookie);
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
             "onUnhandledDrop: %s", dragEvent)
         if (callback == null) {
             wmCallback.notifyUnhandledDropComplete(false)
+            Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "GlobalDragListener.onUnhandledDrop",
+                traceCookie);
             return
         }
 
@@ -112,6 +120,8 @@
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
                 "Notifying onUnhandledDrop complete: %b", it)
             wmCallback.notifyUnhandledDropComplete(it)
+            Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "GlobalDragListener.onUnhandledDrop",
+                traceCookie);
         }
     }
 
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..2b433e9 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
@@ -85,6 +85,9 @@
     /** 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 86c8f04..3e34c30 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
@@ -485,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);
@@ -1146,6 +1152,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/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 36368df9..41890df 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
@@ -1592,6 +1592,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
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/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index ccd0b2d..d83e6b6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -496,6 +496,7 @@
             if (mode == TRANSIT_TO_FRONT) {
                 // When the window is moved to front, make sure the crop is updated to prevent it
                 // from using the old crop.
+                t.setPosition(leash, change.getEndRelOffset().x, change.getEndRelOffset().y);
                 t.setWindowCrop(leash, change.getEndAbsBounds().width(),
                         change.getEndAbsBounds().height());
             }
@@ -507,6 +508,10 @@
                     t.setMatrix(leash, 1, 0, 0, 1);
                     t.setAlpha(leash, 1.f);
                     t.setPosition(leash, change.getEndRelOffset().x, change.getEndRelOffset().y);
+                    t.setWindowCrop(leash, change.getEndAbsBounds().width(),
+                            change.getEndAbsBounds().height());
+                } else {
+                    t.hide(leash);
                 }
                 continue;
             }
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/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 918cefb..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;
@@ -1055,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
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/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/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/hwui/Android.bp b/libs/hwui/Android.bp
index 4486f55..014b841 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -547,6 +547,7 @@
         "hwui/MinikinUtils.cpp",
         "hwui/PaintImpl.cpp",
         "hwui/Typeface.cpp",
+        "thread/CommonPool.cpp",
         "utils/Blur.cpp",
         "utils/Color.cpp",
         "utils/LinearAllocator.cpp",
@@ -563,6 +564,7 @@
         "FrameInfoVisualizer.cpp",
         "FrameMetricsReporter.cpp",
         "Gainmap.cpp",
+        "HWUIProperties.sysprop",
         "Interpolator.cpp",
         "JankTracker.cpp",
         "LightingInfo.cpp",
@@ -622,13 +624,11 @@
                 "renderthread/RenderThread.cpp",
                 "renderthread/HintSessionWrapper.cpp",
                 "service/GraphicsStatsService.cpp",
-                "thread/CommonPool.cpp",
                 "utils/GLUtils.cpp",
                 "utils/NdkUtils.cpp",
                 "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/libs/hwui/platform/android/thread/CommonPoolBase.h b/libs/hwui/platform/android/thread/CommonPoolBase.h
new file mode 100644
index 0000000..8f836b6
--- /dev/null
+++ b/libs/hwui/platform/android/thread/CommonPoolBase.h
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+
+#ifndef FRAMEWORKS_BASE_COMMONPOOLBASE_H
+#define FRAMEWORKS_BASE_COMMONPOOLBASE_H
+
+#include <sys/resource.h>
+
+#include "renderthread/RenderThread.h"
+
+namespace android {
+namespace uirenderer {
+
+class CommonPoolBase {
+    PREVENT_COPY_AND_ASSIGN(CommonPoolBase);
+
+protected:
+    CommonPoolBase() {}
+
+    void setupThread(int i, std::mutex& mLock, std::vector<int>& tids,
+                     std::vector<std::condition_variable>& tidConditionVars) {
+        std::array<char, 20> name{"hwuiTask"};
+        snprintf(name.data(), name.size(), "hwuiTask%d", i);
+        auto self = pthread_self();
+        pthread_setname_np(self, name.data());
+        {
+            std::unique_lock lock(mLock);
+            tids[i] = pthread_gettid_np(self);
+            tidConditionVars[i].notify_one();
+        }
+        setpriority(PRIO_PROCESS, 0, PRIORITY_FOREGROUND);
+        auto startHook = renderthread::RenderThread::getOnStartHook();
+        if (startHook) {
+            startHook(name.data());
+        }
+    }
+
+    bool supportsTid() { return true; }
+};
+
+}  // namespace uirenderer
+}  // namespace android
+
+#endif  // FRAMEWORKS_BASE_COMMONPOOLBASE_H
diff --git a/libs/hwui/platform/host/thread/CommonPoolBase.h b/libs/hwui/platform/host/thread/CommonPoolBase.h
new file mode 100644
index 0000000..cd09101
--- /dev/null
+++ b/libs/hwui/platform/host/thread/CommonPoolBase.h
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+#ifndef FRAMEWORKS_BASE_COMMONPOOLBASE_H
+#define FRAMEWORKS_BASE_COMMONPOOLBASE_H
+
+#include <condition_variable>
+#include <mutex>
+#include <vector>
+
+#include "renderthread/RenderThread.h"
+
+namespace android {
+namespace uirenderer {
+
+class CommonPoolBase {
+    PREVENT_COPY_AND_ASSIGN(CommonPoolBase);
+
+protected:
+    CommonPoolBase() {}
+
+    void setupThread(int i, std::mutex& mLock, std::vector<int>& tids,
+                     std::vector<std::condition_variable>& tidConditionVars) {
+        std::array<char, 20> name{"hwuiTask"};
+        snprintf(name.data(), name.size(), "hwuiTask%d", i);
+        {
+            std::unique_lock lock(mLock);
+            tids[i] = -1;
+            tidConditionVars[i].notify_one();
+        }
+        auto startHook = renderthread::RenderThread::getOnStartHook();
+        if (startHook) {
+            startHook(name.data());
+        }
+    }
+
+    bool supportsTid() { return false; }
+};
+
+}  // namespace uirenderer
+}  // namespace android
+
+#endif  // FRAMEWORKS_BASE_COMMONPOOLBASE_H
diff --git a/libs/hwui/thread/CommonPool.cpp b/libs/hwui/thread/CommonPool.cpp
index dc92f9f..6c0c30f 100644
--- a/libs/hwui/thread/CommonPool.cpp
+++ b/libs/hwui/thread/CommonPool.cpp
@@ -16,16 +16,14 @@
 
 #include "CommonPool.h"
 
-#include <sys/resource.h>
 #include <utils/Trace.h>
-#include "renderthread/RenderThread.h"
 
 #include <array>
 
 namespace android {
 namespace uirenderer {
 
-CommonPool::CommonPool() {
+CommonPool::CommonPool() : CommonPoolBase() {
     ATRACE_CALL();
 
     CommonPool* pool = this;
@@ -36,22 +34,7 @@
     // Create 2 workers
     for (int i = 0; i < THREAD_COUNT; i++) {
         std::thread worker([pool, i, &mLock, &tids, &tidConditionVars] {
-            {
-                std::array<char, 20> name{"hwuiTask"};
-                snprintf(name.data(), name.size(), "hwuiTask%d", i);
-                auto self = pthread_self();
-                pthread_setname_np(self, name.data());
-                {
-                    std::unique_lock lock(mLock);
-                    tids[i] = pthread_gettid_np(self);
-                    tidConditionVars[i].notify_one();
-                }
-                setpriority(PRIO_PROCESS, 0, PRIORITY_FOREGROUND);
-                auto startHook = renderthread::RenderThread::getOnStartHook();
-                if (startHook) {
-                    startHook(name.data());
-                }
-            }
+            pool->setupThread(i, mLock, tids, tidConditionVars);
             pool->workerLoop();
         });
         worker.detach();
@@ -64,7 +47,9 @@
             }
         }
     }
-    mWorkerThreadIds = std::move(tids);
+    if (pool->supportsTid()) {
+        mWorkerThreadIds = std::move(tids);
+    }
 }
 
 CommonPool& CommonPool::instance() {
@@ -95,7 +80,7 @@
 
 void CommonPool::workerLoop() {
     std::unique_lock lock(mLock);
-    while (true) {
+    while (!mIsStopping) {
         if (!mWorkQueue.hasWork()) {
             mWaitingThreads++;
             mCondition.wait(lock);
diff --git a/libs/hwui/thread/CommonPool.h b/libs/hwui/thread/CommonPool.h
index 74f852b..0c025b4 100644
--- a/libs/hwui/thread/CommonPool.h
+++ b/libs/hwui/thread/CommonPool.h
@@ -17,8 +17,6 @@
 #ifndef FRAMEWORKS_BASE_COMMONPOOL_H
 #define FRAMEWORKS_BASE_COMMONPOOL_H
 
-#include "utils/Macros.h"
-
 #include <log/log.h>
 
 #include <condition_variable>
@@ -27,6 +25,9 @@
 #include <mutex>
 #include <vector>
 
+#include "thread/CommonPoolBase.h"
+#include "utils/Macros.h"
+
 namespace android {
 namespace uirenderer {
 
@@ -73,7 +74,7 @@
     int mTail = 0;
 };
 
-class CommonPool {
+class CommonPool : private CommonPoolBase {
     PREVENT_COPY_AND_ASSIGN(CommonPool);
 
 public:
@@ -107,7 +108,10 @@
     static CommonPool& instance();
 
     CommonPool();
-    ~CommonPool() {}
+    ~CommonPool() {
+        mIsStopping = true;
+        mCondition.notify_all();
+    }
 
     void enqueue(Task&&);
     void doWaitForIdle();
@@ -120,6 +124,7 @@
     std::condition_variable mCondition;
     int mWaitingThreads = 0;
     ArrayQueue<Task, QUEUE_SIZE> mWorkQueue;
+    std::atomic_bool mIsStopping = false;
 };
 
 }  // namespace uirenderer
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/MediaCodec.java b/media/java/android/media/MediaCodec.java
index f5dc6ea..1905fa8 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -5101,7 +5101,7 @@
      * size is larger than 16x16, then the qpOffset information of all 16x16 blocks that
      * encompass the coding unit is combined and used. The QP of target block will be calculated
      * as 'frameQP + offsetQP'. If the result exceeds minQP or maxQP configured then the value
-     * may be clamped. Negative offset results in blocks encoded at lower QP than frame QP and
+     * will be clamped. Negative offset results in blocks encoded at lower QP than frame QP and
      * positive offsets will result in encoding blocks at higher QP than frame QP. If the areas
      * of negative QP and positive QP are chosen wisely, the overall viewing experience can be
      * improved.
@@ -5128,7 +5128,7 @@
      * quantization parameter (QP) offset of the blocks in the bounding box. The bounding box
      * will get stretched outwards to align to LCU boundaries during encoding. The Qp Offset is
      * integral and shall be in the range [-128, 127]. The QP of target block will be calculated
-     * as frameQP + offsetQP. If the result exceeds minQP or maxQP configured then the value may
+     * as frameQP + offsetQP. If the result exceeds minQP or maxQP configured then the value will
      * be clamped. Negative offset results in blocks encoded at lower QP than frame QP and
      * positive offsets will result in blocks encoded at higher QP than frame QP. If the areas of
      * negative QP and positive QP are chosen wisely, the overall viewing experience can be
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 1e7bc47..abad460 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -2651,11 +2651,10 @@
             mBlockAspectRatioRange = POSITIVE_RATIONALS;
             mAspectRatioRange      = POSITIVE_RATIONALS;
 
-            // YUV 4:2:0 requires 2:2 alignment
-            mWidthAlignment = 2;
-            mHeightAlignment = 2;
-            mBlockWidth = 2;
-            mBlockHeight = 2;
+            mWidthAlignment = 1;
+            mHeightAlignment = 1;
+            mBlockWidth = 1;
+            mBlockHeight = 1;
             mSmallerDimensionUpperLimit = getSizeRange().getUpper();
         }
 
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/services/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
index d0fee44..7bdc1a0 100644
--- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
@@ -163,10 +163,8 @@
      * Check if we're currently attempting to reboot for a factory reset. This method must
      * return true if RescueParty tries to reboot early during a boot loop, since the device
      * will not be fully booted at this time.
-     *
-     * TODO(gavincorkery): Rename method since its scope has expanded.
      */
-    public static boolean isAttemptingFactoryReset() {
+    public static boolean isRecoveryTriggeredReboot() {
         return isFactoryResetPropertySet() || isRebootPropertySet();
     }
 
diff --git a/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml b/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml
index 685e259..0ae9c26 100644
--- a/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml
+++ b/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml
@@ -19,7 +19,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_height="wrap_content"
     android:layout_width="match_parent"
-    android:importantForAccessibility="noHideDescendants"
+    android:importantForAccessibility="no"
     android:gravity="center"
     android:orientation="horizontal">
 
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
index f4d4dba..815a101 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
@@ -24,6 +24,7 @@
 import android.graphics.drawable.AnimationDrawable;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
@@ -33,16 +34,18 @@
 import android.widget.ImageView;
 
 import androidx.annotation.RawRes;
+import androidx.annotation.StringRes;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceViewHolder;
 import androidx.vectordrawable.graphics.drawable.Animatable2Compat;
 
+import com.android.settingslib.widget.preference.illustration.R;
+
 import com.airbnb.lottie.LottieAnimationView;
 import com.airbnb.lottie.LottieDrawable;
 
 import java.io.FileNotFoundException;
 import java.io.InputStream;
-import com.android.settingslib.widget.preference.illustration.R;
 
 /**
  * IllustrationPreference is a preference that can play lottie format animation
@@ -62,8 +65,8 @@
     private Drawable mImageDrawable;
     private View mMiddleGroundView;
     private OnBindListener mOnBindListener;
-
     private boolean mLottieDynamicColor;
+    private CharSequence mContentDescription;
 
     /**
      * Interface to listen in on when {@link #onBindViewHolder(PreferenceViewHolder)} occurs.
@@ -123,7 +126,10 @@
                 (FrameLayout) holder.findViewById(R.id.middleground_layout);
         final LottieAnimationView illustrationView =
                 (LottieAnimationView) holder.findViewById(R.id.lottie_view);
-
+        if (illustrationView != null && !TextUtils.isEmpty(mContentDescription)) {
+            illustrationView.setContentDescription(mContentDescription);
+            illustrationView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+        }
         // To solve the problem of non-compliant illustrations, we set the frame height
         // to 300dp and set the length of the short side of the screen to
         // the width of the frame.
@@ -208,6 +214,29 @@
     }
 
     /**
+     * To set content description of the {@link Illustration Preference}. This can use for talkback
+     * environment if developer wants to have a customization content.
+     *
+     * @param contentDescription The CharSequence of the content description.
+     */
+    public void setContentDescription(CharSequence contentDescription) {
+        if (!TextUtils.equals(mContentDescription, contentDescription)) {
+            mContentDescription = contentDescription;
+            notifyChanged();
+        }
+    }
+
+    /**
+     * To set content description of the {@link Illustration Preference}. This can use for talkback
+     * environment if developer wants to have a customization content.
+     *
+     * @param contentDescriptionResId The resource id of the content description.
+     */
+    public void setContentDescription(@StringRes int contentDescriptionResId) {
+        setContentDescription(getContext().getText(contentDescriptionResId));
+    }
+
+    /**
      * Gets the lottie illustration resource id.
      */
     public int getLottieAnimationResId() {
diff --git a/packages/SettingsLib/Spa/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/SettingsScreenshotTestRule.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
index 16f6b5e..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
@@ -50,19 +50,15 @@
             )
         )
     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/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/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index a617bf3..fa27db9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -60,6 +60,7 @@
         public CharSequence description;
         public Drawable previewImage;
         public boolean supportsComplications = false;
+        public int dreamCategory;
 
         @Override
         public String toString() {
@@ -207,6 +208,7 @@
                 dreamInfo.settingsComponentName = dreamMetadata.settingsActivity;
                 dreamInfo.previewImage = dreamMetadata.previewImage;
                 dreamInfo.supportsComplications = dreamMetadata.showComplications;
+                dreamInfo.dreamCategory = dreamMetadata.dreamCategory;
             }
             dreamInfos.add(dreamInfo);
         }
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..6730aad 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
@@ -34,6 +34,7 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.conflate
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.filterIsInstance
 import kotlinx.coroutines.flow.filterNotNull
@@ -134,6 +135,7 @@
             }
             .map { getCurrentAudioStream(audioStream) }
             .onStart { emit(getCurrentAudioStream(audioStream)) }
+            .conflate()
             .flowOn(backgroundCoroutineContext)
     }
 
@@ -161,11 +163,11 @@
 
     override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) =
         withContext(backgroundCoroutineContext) {
-            if (isMuted) {
-                audioManager.adjustStreamVolume(audioStream.value, 0, AudioManager.ADJUST_MUTE)
-            } else {
-                audioManager.adjustStreamVolume(audioStream.value, 0, AudioManager.ADJUST_UNMUTE)
-            }
+            audioManager.adjustStreamVolume(
+                audioStream.value,
+                if (isMuted) AudioManager.ADJUST_MUTE else AudioManager.ADJUST_UNMUTE,
+                0,
+            )
         }
 
     private fun getMinVolume(stream: AudioStream): Int =
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
index 1586b8f..c9ac97d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
@@ -84,10 +84,10 @@
                     (audioStreamModel.audioStream.value == AudioManager.STREAM_NOTIFICATION &&
                         audioStreamModel.isMuted)
             ) {
-                return 0
+                return audioStreamModel.minVolume
             }
         } else if (audioStreamModel.isMuted) {
-            return 0
+            return audioStreamModel.minVolume
         }
         return audioStreamModel.volume
     }
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
index 1728a80..9860cd8 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
@@ -77,13 +77,13 @@
         `when`(audioManager.getStreamMaxVolume(anyInt())).thenReturn(MAX_VOLUME)
         `when`(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL)
         `when`(audioManager.setStreamVolume(anyInt(), anyInt(), anyInt())).then {
-            val streamType = it.arguments[1] as Int
-            volumeByStream[it.arguments[0] as Int] = streamType
+            val streamType = it.arguments[0] as Int
+            volumeByStream[it.arguments[0] as Int] = it.arguments[1] as Int
             triggerEvent(AudioManagerEvent.StreamVolumeChanged(AudioStream(streamType)))
         }
         `when`(audioManager.adjustStreamVolume(anyInt(), anyInt(), anyInt())).then {
             val streamType = it.arguments[0] as Int
-            isMuteByStream[streamType] = it.arguments[2] == AudioManager.ADJUST_MUTE
+            isMuteByStream[streamType] = it.arguments[1] == AudioManager.ADJUST_MUTE
             triggerEvent(AudioManagerEvent.StreamMuteChanged(AudioStream(streamType)))
         }
         `when`(audioManager.getStreamVolume(anyInt())).thenAnswer {
diff --git a/packages/SettingsProvider/res/values/strings.xml b/packages/SettingsProvider/res/values/strings.xml
index 76bea31..9ca575e 100644
--- a/packages/SettingsProvider/res/values/strings.xml
+++ b/packages/SettingsProvider/res/values/strings.xml
@@ -19,14 +19,4 @@
 <resources>
     <!-- Name of the activity for Settings storage. -->
     <string name="app_label">Settings Storage</string>
-
-    <!-- A notification is shown when the user's softap config has been changed due to underlying
-     hardware restrictions. This is the notifications's title.
-     [CHAR_LIMIT=NONE] -->
-    <string name="wifi_softap_config_change">Hotspot settings have changed</string>
-
-    <!-- A notification is shown when the user's softap config has been changed due to underlying
-         hardware restrictions. This is the notification's summary message.
-         [CHAR_LIMIT=NONE] -->
-    <string name="wifi_softap_config_change_summary">Tap to see details</string>
 </resources>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 7b49608..e5d62f8 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -1133,16 +1133,15 @@
             // Depending on device hardware, we may need to notify the user of a setting change
             SoftApConfiguration storedConfig = mWifiManager.getSoftApConfiguration();
 
-            if (isNeedToNotifyUserConfigurationHasChanged(configInCloud, storedConfig)) {
-                Log.d(TAG, "restored ap configuration requires a conversion, notify the user"
+            if (isConfigurationHasChanged(configInCloud, storedConfig)) {
+                Log.d(TAG, "restored ap configuration requires a conversion: "
                         + ", configInCloud is " + configInCloud + " but storedConfig is "
                         + storedConfig);
-                WifiSoftApConfigChangedNotifier.notifyUserOfConfigConversion(this);
             }
         }
     }
 
-    private boolean isNeedToNotifyUserConfigurationHasChanged(SoftApConfiguration configInCloud,
+    private boolean isConfigurationHasChanged(SoftApConfiguration configInCloud,
             SoftApConfiguration storedConfig) {
         // Check if the cloud configuration was modified when restored to the device.
         // All elements of the configuration are compared except:
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/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 43ea3ec..02d19dc 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -564,7 +564,7 @@
     <uses-permission android:name="android.permission.TEST_BIOMETRIC" />
 
     <!-- Permission required for CTS test - android.server.biometrics -->
-    <uses-permission android:name="android.permission.SET_BIOMETRIC_DIALOG_LOGO" />
+    <uses-permission android:name="android.permission.SET_BIOMETRIC_DIALOG_ADVANCED" />
 
     <!-- Permissions required for CTS test - NotificationManagerTest -->
     <uses-permission android:name="android.permission.MANAGE_NOTIFICATION_LISTENERS" />
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index e62c77dc..d9c371a 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -434,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"
@@ -529,15 +529,6 @@
             </intent-filter>
         </activity-alias>
 
-        <!-- Springboard for launching the share and edit activity. This needs to be in the main
-             system ui process since we need to notify the status bar to dismiss the keyguard -->
-        <receiver android:name=".screenshot.ActionProxyReceiver"
-            android:exported="false" />
-
-        <!-- Callback for deleting screenshot notification -->
-        <receiver android:name=".screenshot.DeleteScreenshotReceiver"
-            android:exported="false" />
-
         <!-- Callback for invoking a smart action from the screenshot notification. -->
         <receiver android:name=".screenshot.SmartActionsReceiver"
                   android:exported="false"/>
@@ -911,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 1c98630..79ae389 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -345,6 +345,16 @@
 }
 
 flag {
+   name: "activity_transition_use_largest_window"
+   namespace: "systemui"
+   description: "Target largest opening window during activity transitions."
+   bug: "323294573"
+   metadata {
+       purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
    name: "centralized_status_bar_height_fix"
    namespace: "systemui"
    description: "Refactors shade header and keyguard status bar to read status bar dimens from a"
@@ -635,3 +645,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/animation/Android.bp b/packages/SystemUI/animation/Android.bp
index 99b7c36..2268d16 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -44,6 +44,7 @@
         "androidx.core_core-animation-nodeps",
         "androidx.core_core-ktx",
         "androidx.annotation_annotation",
+        "com_android_systemui_flags_lib",
         "SystemUIShaderLib",
         "WindowManager-Shell-shared",
         "animationlib",
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
index 1b99e19..ea1cb34 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
@@ -43,6 +43,7 @@
 import com.android.app.animation.Interpolators
 import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.policy.ScreenDecorationsUtils
+import com.android.systemui.Flags.activityTransitionUseLargestWindow
 import kotlin.math.roundToInt
 
 private const val TAG = "ActivityTransitionAnimator"
@@ -648,11 +649,27 @@
             var candidate: RemoteAnimationTarget? = null
             for (it in apps) {
                 if (it.mode == RemoteAnimationTarget.MODE_OPENING) {
-                    if (!it.hasAnimatingParent) {
-                        return it
-                    }
-                    if (candidate == null) {
-                        candidate = it
+                    if (activityTransitionUseLargestWindow()) {
+                        if (
+                            candidate == null ||
+                                !it.hasAnimatingParent && candidate.hasAnimatingParent
+                        ) {
+                            candidate = it
+                            continue
+                        }
+                        if (
+                            !it.hasAnimatingParent &&
+                                it.screenSpaceBounds.hasGreaterAreaThan(candidate.screenSpaceBounds)
+                        ) {
+                            candidate = it
+                        }
+                    } else {
+                        if (!it.hasAnimatingParent) {
+                            return it
+                        }
+                        if (candidate == null) {
+                            candidate = it
+                        }
                     }
                 }
             }
@@ -960,5 +977,9 @@
                 e.printStackTrace()
             }
         }
+
+        private fun Rect.hasGreaterAreaThan(other: Rect): Boolean {
+            return (this.width() * this.height()) > (other.width() * other.height())
+        }
     }
 }
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..596a297 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(
@@ -239,6 +241,7 @@
         measurePolicy =
             TrackMeasurePolicy(
                 sliderState = sliderState,
+                enabled = enabled,
                 thumbSize = LocalDensity.current.run { thumbSize.roundToPx() },
                 isRtl = isRtl,
                 onDrawingStateMeasured = { drawingState = it }
@@ -302,6 +305,7 @@
 /** Measures track components sizes and calls [onDrawingStateMeasured] when it's done. */
 private class TrackMeasurePolicy(
     private val sliderState: SliderState,
+    private val enabled: Boolean,
     private val thumbSize: Int,
     private val isRtl: Boolean,
     private val onDrawingStateMeasured: (DrawingState) -> Unit,
@@ -332,7 +336,7 @@
                 )
 
         val iconSize = iconPlaceable?.width ?: 0
-        val labelMaxWidth = (desiredWidth - iconSize) / 2
+        val labelMaxWidth = if (enabled) (desiredWidth - iconSize) / 2 else desiredWidth - iconSize
         val labelPlaceable: Placeable? =
             measurables
                 .fastFirstOrNull { it.layoutId == TrackComponent.Label }
@@ -452,11 +456,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 +471,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/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt
index 53f400f..55f7f69a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt
@@ -19,7 +19,6 @@
 import com.android.systemui.keyguard.ui.composable.blueprint.CommunalBlueprintModule
 import com.android.systemui.keyguard.ui.composable.blueprint.DefaultBlueprintModule
 import com.android.systemui.keyguard.ui.composable.blueprint.ShortcutsBesideUdfpsBlueprintModule
-import com.android.systemui.keyguard.ui.composable.blueprint.SplitShadeBlueprintModule
 import com.android.systemui.keyguard.ui.composable.blueprint.SplitShadeWeatherClockBlueprintModule
 import com.android.systemui.keyguard.ui.composable.blueprint.WeatherClockBlueprintModule
 import com.android.systemui.keyguard.ui.composable.section.OptionalSectionModule
@@ -32,7 +31,6 @@
             DefaultBlueprintModule::class,
             OptionalSectionModule::class,
             ShortcutsBesideUdfpsBlueprintModule::class,
-            SplitShadeBlueprintModule::class,
             SplitShadeWeatherClockBlueprintModule::class,
             WeatherClockBlueprintModule::class,
         ],
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt
index c5ff859..d9ed497 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt
@@ -38,6 +38,9 @@
         from(ClockScenes.largeClockScene, to = ClockScenes.smallClockScene) {
             transitioningToSmallClock()
         }
+        from(ClockScenes.splitShadeLargeClockScene, to = ClockScenes.largeClockScene) {
+            spec = tween(1000)
+        }
     }
 
     private fun TransitionBuilder.transitioningToLargeClock() {
@@ -70,6 +73,8 @@
 object ClockScenes {
     val smallClockScene = SceneKey("small-clock-scene")
     val largeClockScene = SceneKey("large-clock-scene")
+    val splitShadeSmallClockScene = SceneKey("split-shade-small-clock-scene")
+    val splitShadeLargeClockScene = SceneKey("split-shade-large-clock-scene")
 }
 
 object ClockElementKeys {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index 9509fd2..320c455 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -22,18 +22,15 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.unit.IntRect
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
 import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
 import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
-import com.android.systemui.keyguard.ui.composable.section.DefaultClockSection
 import com.android.systemui.keyguard.ui.composable.section.LockSection
-import com.android.systemui.keyguard.ui.composable.section.MediaCarouselSection
-import com.android.systemui.keyguard.ui.composable.section.NotificationSection
 import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
 import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
+import com.android.systemui.keyguard.ui.composable.section.TopAreaSection
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
 import dagger.Binds
 import dagger.Module
@@ -50,13 +47,11 @@
 constructor(
     private val viewModel: LockscreenContentViewModel,
     private val statusBarSection: StatusBarSection,
-    private val clockSection: DefaultClockSection,
-    private val notificationSection: NotificationSection,
     private val lockSection: LockSection,
     private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
     private val bottomAreaSection: BottomAreaSection,
     private val settingsMenuSection: SettingsMenuSection,
-    private val mediaCarouselSection: MediaCarouselSection,
+    private val topAreaSection: TopAreaSection,
 ) : ComposableLockscreenSceneBlueprint {
 
     override val id: String = "default"
@@ -64,7 +59,6 @@
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
         val isUdfpsVisible = viewModel.isUdfpsVisible
-        val resources = LocalContext.current.resources
 
         LockscreenLongPress(
             viewModel = viewModel.longPress,
@@ -77,17 +71,7 @@
                         modifier = Modifier.fillMaxWidth(),
                     ) {
                         with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
-                        with(clockSection) { DefaultClockLayout() }
-                        with(mediaCarouselSection) { MediaCarousel() }
-
-                        if (viewModel.areNotificationsVisible(resources = resources)) {
-                            with(notificationSection) {
-                                Notifications(
-                                    modifier = Modifier.fillMaxWidth().weight(weight = 1f)
-                                )
-                            }
-                        }
-
+                        with(topAreaSection) { DefaultClockLayoutWithNotifications() }
                         if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
                             with(ambientIndicationSectionOptional.get()) {
                                 AmbientIndication(modifier = Modifier.fillMaxWidth())
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
index 9abfa42..64c2cb3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -22,18 +22,15 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.unit.IntRect
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
 import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
 import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
-import com.android.systemui.keyguard.ui.composable.section.DefaultClockSection
 import com.android.systemui.keyguard.ui.composable.section.LockSection
-import com.android.systemui.keyguard.ui.composable.section.MediaCarouselSection
-import com.android.systemui.keyguard.ui.composable.section.NotificationSection
 import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
 import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
+import com.android.systemui.keyguard.ui.composable.section.TopAreaSection
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
 import dagger.Binds
 import dagger.Module
@@ -50,13 +47,11 @@
 constructor(
     private val viewModel: LockscreenContentViewModel,
     private val statusBarSection: StatusBarSection,
-    private val clockSection: DefaultClockSection,
-    private val notificationSection: NotificationSection,
     private val lockSection: LockSection,
     private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
     private val bottomAreaSection: BottomAreaSection,
     private val settingsMenuSection: SettingsMenuSection,
-    private val mediaCarouselSection: MediaCarouselSection,
+    private val topAreaSection: TopAreaSection,
 ) : ComposableLockscreenSceneBlueprint {
 
     override val id: String = "shortcuts-besides-udfps"
@@ -64,7 +59,6 @@
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
         val isUdfpsVisible = viewModel.isUdfpsVisible
-        val resources = LocalContext.current.resources
 
         LockscreenLongPress(
             viewModel = viewModel.longPress,
@@ -77,16 +71,7 @@
                         modifier = Modifier.fillMaxWidth(),
                     ) {
                         with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
-                        with(clockSection) { DefaultClockLayout() }
-                        with(mediaCarouselSection) { MediaCarousel() }
-
-                        if (viewModel.areNotificationsVisible(resources = resources)) {
-                            with(notificationSection) {
-                                Notifications(
-                                    modifier = Modifier.fillMaxWidth().weight(weight = 1f)
-                                )
-                            }
-                        }
+                        with(topAreaSection) { DefaultClockLayoutWithNotifications() }
 
                         if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
                             with(ambientIndicationSectionOptional.get()) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
deleted file mode 100644
index 652412d..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * 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.systemui.keyguard.ui.composable.blueprint
-
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.IntRect
-import androidx.compose.ui.unit.dp
-import com.android.compose.animation.scene.SceneScope
-import com.android.compose.modifiers.padding
-import com.android.systemui.Flags
-import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
-import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
-import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
-import com.android.systemui.keyguard.ui.composable.section.DefaultClockSection
-import com.android.systemui.keyguard.ui.composable.section.LockSection
-import com.android.systemui.keyguard.ui.composable.section.MediaCarouselSection
-import com.android.systemui.keyguard.ui.composable.section.NotificationSection
-import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
-import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
-import com.android.systemui.res.R
-import com.android.systemui.shade.LargeScreenHeaderHelper
-import dagger.Binds
-import dagger.Module
-import dagger.multibindings.IntoSet
-import java.util.Optional
-import javax.inject.Inject
-
-/**
- * Renders the lockscreen scene when showing with a split shade (e.g. unfolded foldable and/or
- * tablet form factor).
- */
-class SplitShadeBlueprint
-@Inject
-constructor(
-    private val viewModel: LockscreenContentViewModel,
-    private val statusBarSection: StatusBarSection,
-    private val clockSection: DefaultClockSection,
-    private val notificationSection: NotificationSection,
-    private val lockSection: LockSection,
-    private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
-    private val bottomAreaSection: BottomAreaSection,
-    private val settingsMenuSection: SettingsMenuSection,
-    private val mediaCarouselSection: MediaCarouselSection,
-    private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
-) : ComposableLockscreenSceneBlueprint {
-
-    override val id: String = "split-shade"
-
-    @Composable
-    override fun SceneScope.Content(modifier: Modifier) {
-        val isUdfpsVisible = viewModel.isUdfpsVisible
-
-        LockscreenLongPress(
-            viewModel = viewModel.longPress,
-            modifier = modifier,
-        ) { onSettingsMenuPlaced ->
-            Layout(
-                content = {
-                    // Constrained to above the lock icon.
-                    Column(
-                        modifier = Modifier.fillMaxSize(),
-                    ) {
-                        with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
-                        Row(
-                            modifier = Modifier.fillMaxSize(),
-                        ) {
-                            Column(
-                                modifier = Modifier.fillMaxHeight().weight(weight = 1f),
-                                horizontalAlignment = Alignment.CenterHorizontally,
-                            ) {
-                                with(clockSection) { DefaultClockLayout() }
-                                with(mediaCarouselSection) { MediaCarousel() }
-                            }
-                            with(notificationSection) {
-                                val splitShadeTopMargin: Dp =
-                                    if (Flags.centralizedStatusBarHeightFix()) {
-                                        largeScreenHeaderHelper.getLargeScreenHeaderHeight().dp
-                                    } else {
-                                        dimensionResource(
-                                            id = R.dimen.large_screen_shade_header_height
-                                        )
-                                    }
-                                Notifications(
-                                    modifier =
-                                        Modifier.fillMaxHeight()
-                                            .weight(weight = 1f)
-                                            .padding(top = splitShadeTopMargin)
-                                )
-                            }
-                        }
-
-                        if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
-                            with(ambientIndicationSectionOptional.get()) {
-                                AmbientIndication(modifier = Modifier.fillMaxWidth())
-                            }
-                        }
-                    }
-
-                    with(lockSection) { LockIcon() }
-
-                    // Aligned to bottom and constrained to below the lock icon.
-                    Column(modifier = Modifier.fillMaxWidth()) {
-                        if (isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
-                            with(ambientIndicationSectionOptional.get()) {
-                                AmbientIndication(modifier = Modifier.fillMaxWidth())
-                            }
-                        }
-
-                        with(bottomAreaSection) {
-                            IndicationArea(modifier = Modifier.fillMaxWidth())
-                        }
-                    }
-
-                    // Aligned to bottom and NOT constrained by the lock icon.
-                    with(bottomAreaSection) {
-                        Shortcut(isStart = true, applyPadding = true)
-                        Shortcut(isStart = false, applyPadding = true)
-                    }
-                    with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) }
-                },
-                modifier = Modifier.fillMaxSize(),
-            ) { measurables, constraints ->
-                check(measurables.size == 6)
-                val aboveLockIconMeasurable = measurables[0]
-                val lockIconMeasurable = measurables[1]
-                val belowLockIconMeasurable = measurables[2]
-                val startShortcutMeasurable = measurables[3]
-                val endShortcutMeasurable = measurables[4]
-                val settingsMenuMeasurable = measurables[5]
-
-                val noMinConstraints =
-                    constraints.copy(
-                        minWidth = 0,
-                        minHeight = 0,
-                    )
-                val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
-                val lockIconBounds =
-                    IntRect(
-                        left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
-                        top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
-                        right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
-                        bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
-                    )
-
-                val aboveLockIconPlaceable =
-                    aboveLockIconMeasurable.measure(
-                        noMinConstraints.copy(maxHeight = lockIconBounds.top)
-                    )
-                val belowLockIconPlaceable =
-                    belowLockIconMeasurable.measure(
-                        noMinConstraints.copy(
-                            maxHeight =
-                                (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0)
-                        )
-                    )
-                val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints)
-                val endShortcutPleaceable = endShortcutMeasurable.measure(noMinConstraints)
-                val settingsMenuPlaceable = settingsMenuMeasurable.measure(noMinConstraints)
-
-                layout(constraints.maxWidth, constraints.maxHeight) {
-                    aboveLockIconPlaceable.place(
-                        x = 0,
-                        y = 0,
-                    )
-                    lockIconPlaceable.place(
-                        x = lockIconBounds.left,
-                        y = lockIconBounds.top,
-                    )
-                    belowLockIconPlaceable.place(
-                        x = 0,
-                        y = constraints.maxHeight - belowLockIconPlaceable.height,
-                    )
-                    startShortcutPleaceable.place(
-                        x = 0,
-                        y = constraints.maxHeight - startShortcutPleaceable.height,
-                    )
-                    endShortcutPleaceable.place(
-                        x = constraints.maxWidth - endShortcutPleaceable.width,
-                        y = constraints.maxHeight - endShortcutPleaceable.height,
-                    )
-                    settingsMenuPlaceable.place(
-                        x = (constraints.maxWidth - settingsMenuPlaceable.width) / 2,
-                        y = constraints.maxHeight - settingsMenuPlaceable.height,
-                    )
-                }
-            }
-        }
-    }
-}
-
-@Module
-interface SplitShadeBlueprintModule {
-    @Binds
-    @IntoSet
-    fun blueprint(blueprint: SplitShadeBlueprint): ComposableLockscreenSceneBlueprint
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt
index f86623f..ee4e2d6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt
@@ -112,7 +112,7 @@
 
                         with(mediaCarouselSection) { MediaCarousel() }
 
-                        if (viewModel.areNotificationsVisible(resources = resources)) {
+                        if (viewModel.areNotificationsVisible) {
                             with(notificationSection) {
                                 Notifications(
                                     modifier = Modifier.fillMaxWidth().weight(weight = 1f)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
index 1ab2bc76..82e19e7c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
@@ -16,39 +16,29 @@
 
 package com.android.systemui.keyguard.ui.composable.section
 
+import android.view.View
 import android.view.ViewGroup
 import android.widget.FrameLayout
-import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.view.contains
 import com.android.compose.animation.scene.SceneScope
-import com.android.compose.animation.scene.SceneTransitionLayout
 import com.android.compose.modifiers.padding
 import com.android.systemui.customization.R as customizationR
-import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.largeClockElementKey
 import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smallClockElementKey
-import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smartspaceElementKey
-import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.largeClockScene
-import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.smallClockScene
-import com.android.systemui.keyguard.ui.composable.blueprint.ClockTransition.defaultClockTransitions
-import com.android.systemui.keyguard.ui.composable.blueprint.rememberBurnIn
 import com.android.systemui.keyguard.ui.composable.modifier.burnInAware
 import com.android.systemui.keyguard.ui.composable.modifier.onTopPlacementChanged
 import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
 import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
-import com.android.systemui.res.R
 import javax.inject.Inject
 
 /** Provides small clock and large clock composables for the default clock face. */
@@ -56,91 +46,10 @@
 @Inject
 constructor(
     private val viewModel: KeyguardClockViewModel,
-    private val clockInteractor: KeyguardClockInteractor,
     private val aodBurnInViewModel: AodBurnInViewModel,
-    private val lockscreenContentViewModel: LockscreenContentViewModel,
-    private val smartSpaceSection: SmartSpaceSection,
 ) {
     @Composable
-    fun DefaultClockLayout(
-        modifier: Modifier = Modifier,
-    ) {
-        val isLargeClockVisible by viewModel.isLargeClockVisible.collectAsState()
-        val burnIn = rememberBurnIn(clockInteractor)
-        val currentScene =
-            if (isLargeClockVisible) {
-                largeClockScene
-            } else {
-                smallClockScene
-            }
-
-        LaunchedEffect(isLargeClockVisible) {
-            if (isLargeClockVisible) {
-                burnIn.onSmallClockTopChanged(null)
-            }
-        }
-
-        SceneTransitionLayout(
-            modifier = modifier,
-            currentScene = currentScene,
-            onChangeScene = {},
-            transitions = defaultClockTransitions,
-        ) {
-            scene(smallClockScene) {
-                Column {
-                    SmallClock(
-                        burnInParams = burnIn.parameters,
-                        onTopChanged = burnIn.onSmallClockTopChanged,
-                        modifier = Modifier.element(smallClockElementKey).fillMaxWidth()
-                    )
-                    SmartSpaceContent()
-                }
-            }
-
-            scene(largeClockScene) {
-                Column {
-                    SmartSpaceContent()
-                    LargeClock(modifier = Modifier.element(largeClockElementKey).fillMaxWidth())
-                }
-            }
-        }
-    }
-
-    @Composable
-    private fun SceneScope.SmartSpaceContent(
-        modifier: Modifier = Modifier,
-    ) {
-        val burnIn = rememberBurnIn(clockInteractor)
-        val resources = LocalContext.current.resources
-
-        MovableElement(key = smartspaceElementKey, modifier = modifier) {
-            content {
-                with(smartSpaceSection) {
-                    this@SmartSpaceContent.SmartSpace(
-                        burnInParams = burnIn.parameters,
-                        onTopChanged = burnIn.onSmartspaceTopChanged,
-                        modifier =
-                            Modifier.fillMaxWidth()
-                                .padding(
-                                    top = {
-                                        lockscreenContentViewModel.getSmartSpacePaddingTop(
-                                            resources
-                                        )
-                                    },
-                                    bottom = {
-                                        resources.getDimensionPixelSize(
-                                            R.dimen.keyguard_status_view_bottom_margin
-                                        )
-                                    }
-                                ),
-                    )
-                }
-            }
-        }
-    }
-
-    @Composable
-    private fun SceneScope.SmallClock(
+    fun SceneScope.SmallClock(
         burnInParams: BurnInParameters,
         onTopChanged: (top: Float?) -> Unit,
         modifier: Modifier = Modifier,
@@ -152,58 +61,60 @@
         viewModel.clock = currentClock
 
         val context = LocalContext.current
-
-        AndroidView(
-            factory = { context ->
-                FrameLayout(context).apply {
-                    val newClockView = checkNotNull(currentClock).smallClock.view
-                    (newClockView.parent as? ViewGroup)?.removeView(newClockView)
-                    addView(newClockView)
-                }
-            },
-            update = {
-                val newClockView = checkNotNull(currentClock).smallClock.view
-                it.removeAllViews()
-                (newClockView.parent as? ViewGroup)?.removeView(newClockView)
-                it.addView(newClockView)
-            },
-            modifier =
-                modifier
-                    .padding(
-                        horizontal = dimensionResource(customizationR.dimen.clock_padding_start)
-                    )
-                    .padding(top = { viewModel.getSmallClockTopMargin(context) })
-                    .onTopPlacementChanged(onTopChanged)
-                    .burnInAware(
-                        viewModel = aodBurnInViewModel,
-                        params = burnInParams,
-                    ),
-        )
+        MovableElement(key = smallClockElementKey, modifier = modifier) {
+            content {
+                AndroidView(
+                    factory = { context ->
+                        FrameLayout(context).apply {
+                            addClockView(checkNotNull(currentClock).smallClock.view)
+                        }
+                    },
+                    update = { it.addClockView(checkNotNull(currentClock).smallClock.view) },
+                    modifier =
+                        Modifier.padding(
+                                horizontal =
+                                    dimensionResource(customizationR.dimen.clock_padding_start)
+                            )
+                            .padding(top = { viewModel.getSmallClockTopMargin(context) })
+                            .onTopPlacementChanged(onTopChanged)
+                            .burnInAware(
+                                viewModel = aodBurnInViewModel,
+                                params = burnInParams,
+                            ),
+                )
+            }
+        }
     }
 
     @Composable
-    private fun SceneScope.LargeClock(modifier: Modifier = Modifier) {
+    fun SceneScope.LargeClock(modifier: Modifier = Modifier) {
         val currentClock by viewModel.currentClock.collectAsState()
         viewModel.clock = currentClock
         if (currentClock?.largeClock?.view == null) {
             return
         }
 
-        AndroidView(
-            factory = { context ->
-                FrameLayout(context).apply {
-                    val newClockView = checkNotNull(currentClock).largeClock.view
-                    (newClockView.parent as? ViewGroup)?.removeView(newClockView)
-                    addView(newClockView)
-                }
-            },
-            update = {
-                val newClockView = checkNotNull(currentClock).largeClock.view
-                it.removeAllViews()
-                (newClockView.parent as? ViewGroup)?.removeView(newClockView)
-                it.addView(newClockView)
-            },
-            modifier = modifier.fillMaxSize()
-        )
+        MovableElement(key = largeClockElementKey, modifier = modifier) {
+            content {
+                AndroidView(
+                    factory = { context ->
+                        FrameLayout(context).apply {
+                            addClockView(checkNotNull(currentClock).largeClock.view)
+                        }
+                    },
+                    update = { it.addClockView(checkNotNull(currentClock).largeClock.view) },
+                    modifier = Modifier.fillMaxSize()
+                )
+            }
+        }
+    }
+
+    private fun FrameLayout.addClockView(clockView: View) {
+        if (contains(clockView)) {
+            return
+        }
+        removeAllViews()
+        (clockView.parent as? ViewGroup)?.removeView(clockView)
+        addView(clockView)
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
index d1cc53e..fc8b3b9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
@@ -30,16 +30,20 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
 import com.android.compose.animation.scene.SceneScope
+import com.android.compose.modifiers.padding
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys
 import com.android.systemui.keyguard.ui.composable.modifier.burnInAware
 import com.android.systemui.keyguard.ui.composable.modifier.onTopPlacementChanged
 import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
 import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
 import javax.inject.Inject
@@ -51,6 +55,7 @@
     private val keyguardUnlockAnimationController: KeyguardUnlockAnimationController,
     private val keyguardSmartspaceViewModel: KeyguardSmartspaceViewModel,
     private val aodBurnInViewModel: AodBurnInViewModel,
+    private val lockscreenContentViewModel: LockscreenContentViewModel,
 ) {
     @Composable
     fun SceneScope.SmartSpace(
@@ -58,57 +63,71 @@
         onTopChanged: (top: Float?) -> Unit,
         modifier: Modifier = Modifier,
     ) {
-        Column(
-            modifier = modifier.onTopPlacementChanged(onTopChanged),
-        ) {
-            if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) {
-                return
-            }
+        val resources = LocalContext.current.resources
 
-            val paddingBelowClockStart = dimensionResource(R.dimen.below_clock_padding_start)
-            val paddingBelowClockEnd = dimensionResource(R.dimen.below_clock_padding_end)
+        MovableElement(key = ClockElementKeys.smartspaceElementKey, modifier = modifier) {
+            Column(
+                modifier =
+                    modifier
+                        .onTopPlacementChanged(onTopChanged)
+                        .padding(
+                            top = { lockscreenContentViewModel.getSmartSpacePaddingTop(resources) },
+                            bottom = {
+                                resources.getDimensionPixelSize(
+                                    R.dimen.keyguard_status_view_bottom_margin
+                                )
+                            }
+                        )
+            ) {
+                if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) {
+                    return@Column
+                }
 
-            if (keyguardSmartspaceViewModel.isDateWeatherDecoupled) {
-                Row(
-                    verticalAlignment = Alignment.CenterVertically,
+                val paddingBelowClockStart = dimensionResource(R.dimen.below_clock_padding_start)
+                val paddingBelowClockEnd = dimensionResource(R.dimen.below_clock_padding_end)
+
+                if (keyguardSmartspaceViewModel.isDateWeatherDecoupled) {
+                    Row(
+                        verticalAlignment = Alignment.CenterVertically,
+                        modifier =
+                            Modifier.fillMaxWidth()
+                                // All items will be constrained to be as tall as the shortest item.
+                                .height(IntrinsicSize.Min)
+                                .padding(
+                                    start = paddingBelowClockStart,
+                                ),
+                    ) {
+                        Date(
+                            modifier =
+                                Modifier.burnInAware(
+                                    viewModel = aodBurnInViewModel,
+                                    params = burnInParams,
+                                ),
+                        )
+                        Spacer(modifier = Modifier.width(4.dp))
+                        Weather(
+                            modifier =
+                                Modifier.burnInAware(
+                                    viewModel = aodBurnInViewModel,
+                                    params = burnInParams,
+                                ),
+                        )
+                    }
+                }
+
+                Card(
                     modifier =
                         Modifier.fillMaxWidth()
-                            // All items will be constrained to be as tall as the shortest item.
-                            .height(IntrinsicSize.Min)
                             .padding(
                                 start = paddingBelowClockStart,
-                            ),
-                ) {
-                    Date(
-                        modifier =
-                            Modifier.burnInAware(
+                                end = paddingBelowClockEnd,
+                            )
+                            .burnInAware(
                                 viewModel = aodBurnInViewModel,
                                 params = burnInParams,
                             ),
-                    )
-                    Spacer(modifier = Modifier.width(4.dp))
-                    Weather(
-                        modifier =
-                            Modifier.burnInAware(
-                                viewModel = aodBurnInViewModel,
-                                params = burnInParams,
-                            ),
-                    )
-                }
+                )
             }
-
-            Card(
-                modifier =
-                    Modifier.fillMaxWidth()
-                        .padding(
-                            start = paddingBelowClockStart,
-                            end = paddingBelowClockEnd,
-                        )
-                        .burnInAware(
-                            viewModel = aodBurnInViewModel,
-                            params = burnInParams,
-                        ),
-            )
         }
     }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
new file mode 100644
index 0000000..7635841
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.composable.section
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.SceneTransitionLayout
+import com.android.systemui.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.largeClockScene
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.smallClockScene
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.splitShadeLargeClockScene
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.splitShadeSmallClockScene
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockTransition
+import com.android.systemui.keyguard.ui.composable.blueprint.rememberBurnIn
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import com.android.systemui.res.R
+import com.android.systemui.shade.LargeScreenHeaderHelper
+import javax.inject.Inject
+
+class TopAreaSection
+@Inject
+constructor(
+    private val clockViewModel: KeyguardClockViewModel,
+    private val smartSpaceSection: SmartSpaceSection,
+    private val mediaCarouselSection: MediaCarouselSection,
+    private val notificationSection: NotificationSection,
+    private val clockSection: DefaultClockSection,
+    private val clockInteractor: KeyguardClockInteractor,
+) {
+    @Composable
+    fun DefaultClockLayoutWithNotifications(
+        modifier: Modifier = Modifier,
+    ) {
+        val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsState()
+        val currentClockLayout by clockViewModel.currentClockLayout.collectAsState()
+        val currentScene =
+            when (currentClockLayout) {
+                KeyguardClockViewModel.ClockLayout.SPLIT_SHADE_LARGE_CLOCK ->
+                    splitShadeLargeClockScene
+                KeyguardClockViewModel.ClockLayout.SPLIT_SHADE_SMALL_CLOCK ->
+                    splitShadeSmallClockScene
+                KeyguardClockViewModel.ClockLayout.LARGE_CLOCK -> largeClockScene
+                KeyguardClockViewModel.ClockLayout.SMALL_CLOCK -> smallClockScene
+            }
+
+        val splitShadeTopMargin: Dp =
+            if (Flags.centralizedStatusBarHeightFix()) {
+                LargeScreenHeaderHelper.getLargeScreenHeaderHeight(LocalContext.current).dp
+            } else {
+                dimensionResource(id = R.dimen.large_screen_shade_header_height)
+            }
+        val burnIn = rememberBurnIn(clockInteractor)
+
+        LaunchedEffect(isLargeClockVisible) {
+            if (isLargeClockVisible) {
+                burnIn.onSmallClockTopChanged(null)
+            }
+        }
+
+        SceneTransitionLayout(
+            modifier = modifier.fillMaxSize(),
+            currentScene = currentScene,
+            onChangeScene = {},
+            transitions = ClockTransition.defaultClockTransitions,
+        ) {
+            scene(ClockScenes.splitShadeLargeClockScene) {
+                Row(
+                    modifier = Modifier.fillMaxSize(),
+                ) {
+                    Column(
+                        modifier = Modifier.fillMaxHeight().weight(weight = 1f),
+                        horizontalAlignment = Alignment.CenterHorizontally,
+                    ) {
+                        with(smartSpaceSection) {
+                            SmartSpace(
+                                burnInParams = burnIn.parameters,
+                                onTopChanged = burnIn.onSmartspaceTopChanged,
+                            )
+                        }
+                        with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
+                    }
+                    with(notificationSection) {
+                        Notifications(
+                            modifier =
+                                Modifier.fillMaxHeight()
+                                    .weight(weight = 1f)
+                                    .padding(top = splitShadeTopMargin)
+                        )
+                    }
+                }
+            }
+
+            scene(ClockScenes.splitShadeSmallClockScene) {
+                Row(
+                    modifier = Modifier.fillMaxSize(),
+                ) {
+                    Column(
+                        modifier = Modifier.fillMaxHeight().weight(weight = 1f),
+                        horizontalAlignment = Alignment.CenterHorizontally,
+                    ) {
+                        with(clockSection) {
+                            SmallClock(
+                                burnInParams = burnIn.parameters,
+                                onTopChanged = burnIn.onSmallClockTopChanged,
+                                modifier = Modifier.fillMaxWidth()
+                            )
+                        }
+                        with(smartSpaceSection) {
+                            SmartSpace(
+                                burnInParams = burnIn.parameters,
+                                onTopChanged = burnIn.onSmartspaceTopChanged,
+                            )
+                        }
+                        with(mediaCarouselSection) { MediaCarousel() }
+                    }
+                    with(notificationSection) {
+                        Notifications(
+                            modifier =
+                                Modifier.fillMaxHeight()
+                                    .weight(weight = 1f)
+                                    .padding(top = splitShadeTopMargin)
+                        )
+                    }
+                }
+            }
+
+            scene(ClockScenes.smallClockScene) {
+                Column {
+                    with(clockSection) {
+                        SmallClock(
+                            burnInParams = burnIn.parameters,
+                            onTopChanged = burnIn.onSmallClockTopChanged,
+                            modifier = Modifier.fillMaxWidth()
+                        )
+                    }
+                    with(smartSpaceSection) {
+                        SmartSpace(
+                            burnInParams = burnIn.parameters,
+                            onTopChanged = burnIn.onSmartspaceTopChanged,
+                        )
+                    }
+                    with(mediaCarouselSection) { MediaCarousel() }
+                    with(notificationSection) {
+                        Notifications(
+                            modifier =
+                                androidx.compose.ui.Modifier.fillMaxWidth().weight(weight = 1f)
+                        )
+                    }
+                }
+            }
+
+            scene(ClockScenes.largeClockScene) {
+                Column {
+                    with(smartSpaceSection) {
+                        SmartSpace(
+                            burnInParams = burnIn.parameters,
+                            onTopChanged = burnIn.onSmartspaceTopChanged,
+                        )
+                    }
+                    with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
+                }
+            }
+        }
+    }
+}
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..2435170 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
@@ -16,21 +16,25 @@
 
 package com.android.systemui.volume.panel.component.volume.ui.composable
 
-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.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,45 +44,81 @@
 @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 = state.valueText, 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,
         label = {
-            Column(modifier = Modifier.animateContentSize()) {
+            Column(modifier = Modifier) {
                 Text(
                     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 },
-                    ) {
+                if (!state.isEnabled) {
+                    state.disabledMessage?.let { message ->
                         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/monet/Android.bp b/packages/SystemUI/monet/Android.bp
index 98f7ace..c54fdab 100644
--- a/packages/SystemUI/monet/Android.bp
+++ b/packages/SystemUI/monet/Android.bp
@@ -24,6 +24,7 @@
     static_libs: [
         "androidx.annotation_annotation",
         "androidx.core_core",
+        "libmonet",
     ],
     srcs: [
         "src/**/*.java",
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
index 46a90f6..47a00f4 100644
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
@@ -20,11 +20,17 @@
 import android.app.WallpaperColors
 import android.graphics.Color
 import com.android.internal.graphics.ColorUtils
-import com.android.internal.graphics.cam.Cam
-import com.android.internal.graphics.cam.CamUtils
+import com.google.ux.material.libmonet.hct.Hct
+import com.google.ux.material.libmonet.scheme.DynamicScheme
+import com.google.ux.material.libmonet.scheme.SchemeContent
+import com.google.ux.material.libmonet.scheme.SchemeExpressive
+import com.google.ux.material.libmonet.scheme.SchemeFruitSalad
+import com.google.ux.material.libmonet.scheme.SchemeMonochrome
+import com.google.ux.material.libmonet.scheme.SchemeNeutral
+import com.google.ux.material.libmonet.scheme.SchemeRainbow
+import com.google.ux.material.libmonet.scheme.SchemeTonalSpot
+import com.google.ux.material.libmonet.scheme.SchemeVibrant
 import kotlin.math.absoluteValue
-import kotlin.math.max
-import kotlin.math.min
 import kotlin.math.roundToInt
 
 const val TAG = "ColorScheme"
@@ -33,347 +39,65 @@
 const val GOOGLE_BLUE = 0xFF1b6ef3.toInt()
 const val MIN_CHROMA = 5
 
-internal interface Hue {
-    fun get(sourceColor: Cam): Double
-
-    /**
-     * Given a hue, and a mapping of hues to hue rotations, find which hues in the mapping the hue
-     * fall betweens, and use the hue rotation of the lower hue.
-     *
-     * @param sourceHue hue of source color
-     * @param hueAndRotations list of pairs, where the first item in a pair is a hue, and the second
-     *   item in the pair is a hue rotation that should be applied
-     */
-    fun getHueRotation(sourceHue: Float, hueAndRotations: List<Pair<Int, Int>>): Double {
-        val sanitizedSourceHue = (if (sourceHue < 0 || sourceHue >= 360) 0 else sourceHue).toFloat()
-        for (i in 0..hueAndRotations.size - 2) {
-            val thisHue = hueAndRotations[i].first.toFloat()
-            val nextHue = hueAndRotations[i + 1].first.toFloat()
-            if (thisHue <= sanitizedSourceHue && sanitizedSourceHue < nextHue) {
-                return ColorScheme.wrapDegreesDouble(
-                    sanitizedSourceHue.toDouble() + hueAndRotations[i].second
-                )
-            }
-        }
-
-        // If this statement executes, something is wrong, there should have been a rotation
-        // found using the arrays.
-        return sourceHue.toDouble()
-    }
-}
-
-internal class HueSource : Hue {
-    override fun get(sourceColor: Cam): Double {
-        return sourceColor.hue.toDouble()
-    }
-}
-
-internal class HueAdd(val amountDegrees: Double) : Hue {
-    override fun get(sourceColor: Cam): Double {
-        return ColorScheme.wrapDegreesDouble(sourceColor.hue.toDouble() + amountDegrees)
-    }
-}
-
-internal class HueSubtract(val amountDegrees: Double) : Hue {
-    override fun get(sourceColor: Cam): Double {
-        return ColorScheme.wrapDegreesDouble(sourceColor.hue.toDouble() - amountDegrees)
-    }
-}
-
-internal class HueVibrantSecondary() : Hue {
-    val hueToRotations =
-        listOf(
-            Pair(0, 18),
-            Pair(41, 15),
-            Pair(61, 10),
-            Pair(101, 12),
-            Pair(131, 15),
-            Pair(181, 18),
-            Pair(251, 15),
-            Pair(301, 12),
-            Pair(360, 12)
-        )
-
-    override fun get(sourceColor: Cam): Double {
-        return getHueRotation(sourceColor.hue, hueToRotations)
-    }
-}
-
-internal class HueVibrantTertiary() : Hue {
-    val hueToRotations =
-        listOf(
-            Pair(0, 35),
-            Pair(41, 30),
-            Pair(61, 20),
-            Pair(101, 25),
-            Pair(131, 30),
-            Pair(181, 35),
-            Pair(251, 30),
-            Pair(301, 25),
-            Pair(360, 25)
-        )
-
-    override fun get(sourceColor: Cam): Double {
-        return getHueRotation(sourceColor.hue, hueToRotations)
-    }
-}
-
-internal class HueExpressiveSecondary() : Hue {
-    val hueToRotations =
-        listOf(
-            Pair(0, 45),
-            Pair(21, 95),
-            Pair(51, 45),
-            Pair(121, 20),
-            Pair(151, 45),
-            Pair(191, 90),
-            Pair(271, 45),
-            Pair(321, 45),
-            Pair(360, 45)
-        )
-
-    override fun get(sourceColor: Cam): Double {
-        return getHueRotation(sourceColor.hue, hueToRotations)
-    }
-}
-
-internal class HueExpressiveTertiary() : Hue {
-    val hueToRotations =
-        listOf(
-            Pair(0, 120),
-            Pair(21, 120),
-            Pair(51, 20),
-            Pair(121, 45),
-            Pair(151, 20),
-            Pair(191, 15),
-            Pair(271, 20),
-            Pair(321, 120),
-            Pair(360, 120)
-        )
-
-    override fun get(sourceColor: Cam): Double {
-        return getHueRotation(sourceColor.hue, hueToRotations)
-    }
-}
-
-internal interface Chroma {
-    fun get(sourceColor: Cam): Double
-
-    companion object {
-        val MAX_VALUE = 120.0
-        val MIN_VALUE = 0.0
-    }
-}
-
-internal class ChromaMaxOut : Chroma {
-    override fun get(sourceColor: Cam): Double {
-        // Intentionally high. Gamut mapping from impossible HCT to sRGB will ensure that
-        // the maximum chroma is reached, even if lower than this constant.
-        return Chroma.MAX_VALUE + 10.0
-    }
-}
-
-internal class ChromaMultiple(val multiple: Double) : Chroma {
-    override fun get(sourceColor: Cam): Double {
-        return sourceColor.chroma * multiple
-    }
-}
-
-internal class ChromaAdd(val amount: Double) : Chroma {
-    override fun get(sourceColor: Cam): Double {
-        return sourceColor.chroma + amount
-    }
-}
-
-internal class ChromaBound(
-    val baseChroma: Chroma,
-    val minVal: Double,
-    val maxVal: Double,
-) : Chroma {
-    override fun get(sourceColor: Cam): Double {
-        val result = baseChroma.get(sourceColor)
-        return min(max(result, minVal), maxVal)
-    }
-}
-
-internal class ChromaConstant(val chroma: Double) : Chroma {
-    override fun get(sourceColor: Cam): Double {
-        return chroma
-    }
-}
-
-internal class ChromaSource : Chroma {
-    override fun get(sourceColor: Cam): Double {
-        return sourceColor.chroma.toDouble()
-    }
-}
-
-internal class TonalSpec(val hue: Hue = HueSource(), val chroma: Chroma) {
-    fun shades(sourceColor: Cam): List<Int> {
-        val hue = hue.get(sourceColor)
-        val chroma = chroma.get(sourceColor)
-        return Shades.of(hue.toFloat(), chroma.toFloat()).toList()
-    }
-
-    fun getAtTone(sourceColor: Cam, tone: Float): Int {
-        val hue = hue.get(sourceColor)
-        val chroma = chroma.get(sourceColor)
-        return ColorUtils.CAMToColor(hue.toFloat(), chroma.toFloat(), (1000f - tone) / 10f)
-    }
-}
-
-internal class CoreSpec(
-    val a1: TonalSpec,
-    val a2: TonalSpec,
-    val a3: TonalSpec,
-    val n1: TonalSpec,
-    val n2: TonalSpec
-)
-
-enum class Style(internal val coreSpec: CoreSpec) {
-    SPRITZ(
-        CoreSpec(
-            a1 = TonalSpec(HueSource(), ChromaConstant(12.0)),
-            a2 = TonalSpec(HueSource(), ChromaConstant(8.0)),
-            a3 = TonalSpec(HueSource(), ChromaConstant(16.0)),
-            n1 = TonalSpec(HueSource(), ChromaConstant(2.0)),
-            n2 = TonalSpec(HueSource(), ChromaConstant(2.0))
-        )
-    ),
-    TONAL_SPOT(
-        CoreSpec(
-            a1 = TonalSpec(HueSource(), ChromaConstant(36.0)),
-            a2 = TonalSpec(HueSource(), ChromaConstant(16.0)),
-            a3 = TonalSpec(HueAdd(60.0), ChromaConstant(24.0)),
-            n1 = TonalSpec(HueSource(), ChromaConstant(6.0)),
-            n2 = TonalSpec(HueSource(), ChromaConstant(8.0))
-        )
-    ),
-    VIBRANT(
-        CoreSpec(
-            a1 = TonalSpec(HueSource(), ChromaMaxOut()),
-            a2 = TonalSpec(HueVibrantSecondary(), ChromaConstant(24.0)),
-            a3 = TonalSpec(HueVibrantTertiary(), ChromaConstant(32.0)),
-            n1 = TonalSpec(HueSource(), ChromaConstant(10.0)),
-            n2 = TonalSpec(HueSource(), ChromaConstant(12.0))
-        )
-    ),
-    EXPRESSIVE(
-        CoreSpec(
-            a1 = TonalSpec(HueAdd(240.0), ChromaConstant(40.0)),
-            a2 = TonalSpec(HueExpressiveSecondary(), ChromaConstant(24.0)),
-            a3 = TonalSpec(HueExpressiveTertiary(), ChromaConstant(32.0)),
-            n1 = TonalSpec(HueAdd(15.0), ChromaConstant(8.0)),
-            n2 = TonalSpec(HueAdd(15.0), ChromaConstant(12.0))
-        )
-    ),
-    RAINBOW(
-        CoreSpec(
-            a1 = TonalSpec(HueSource(), ChromaConstant(48.0)),
-            a2 = TonalSpec(HueSource(), ChromaConstant(16.0)),
-            a3 = TonalSpec(HueAdd(60.0), ChromaConstant(24.0)),
-            n1 = TonalSpec(HueSource(), ChromaConstant(0.0)),
-            n2 = TonalSpec(HueSource(), ChromaConstant(0.0))
-        )
-    ),
-    FRUIT_SALAD(
-        CoreSpec(
-            a1 = TonalSpec(HueSubtract(50.0), ChromaConstant(48.0)),
-            a2 = TonalSpec(HueSubtract(50.0), ChromaConstant(36.0)),
-            a3 = TonalSpec(HueSource(), ChromaConstant(36.0)),
-            n1 = TonalSpec(HueSource(), ChromaConstant(10.0)),
-            n2 = TonalSpec(HueSource(), ChromaConstant(16.0))
-        )
-    ),
-    CONTENT(
-        CoreSpec(
-            a1 = TonalSpec(HueSource(), ChromaSource()),
-            a2 = TonalSpec(HueSource(), ChromaMultiple(0.33)),
-            a3 = TonalSpec(HueSource(), ChromaMultiple(0.66)),
-            n1 = TonalSpec(HueSource(), ChromaMultiple(0.0833)),
-            n2 = TonalSpec(HueSource(), ChromaMultiple(0.1666))
-        )
-    ),
-    MONOCHROMATIC(
-        CoreSpec(
-            a1 = TonalSpec(HueSource(), ChromaConstant(.0)),
-            a2 = TonalSpec(HueSource(), ChromaConstant(.0)),
-            a3 = TonalSpec(HueSource(), ChromaConstant(.0)),
-            n1 = TonalSpec(HueSource(), ChromaConstant(.0)),
-            n2 = TonalSpec(HueSource(), ChromaConstant(.0))
-        )
-    ),
-    CLOCK(
-        CoreSpec(
-            a1 = TonalSpec(HueSource(), ChromaBound(ChromaSource(), 20.0, Chroma.MAX_VALUE)),
-            a2 = TonalSpec(HueAdd(10.0), ChromaBound(ChromaMultiple(0.85), 17.0, 40.0)),
-            a3 = TonalSpec(HueAdd(20.0), ChromaBound(ChromaAdd(20.0), 50.0, Chroma.MAX_VALUE)),
-
-            // Not Used
-            n1 = TonalSpec(HueSource(), ChromaConstant(0.0)),
-            n2 = TonalSpec(HueSource(), ChromaConstant(0.0))
-        )
-    ),
-    CLOCK_VIBRANT(
-        CoreSpec(
-            a1 = TonalSpec(HueSource(), ChromaBound(ChromaSource(), 70.0, Chroma.MAX_VALUE)),
-            a2 = TonalSpec(HueAdd(20.0), ChromaBound(ChromaSource(), 70.0, Chroma.MAX_VALUE)),
-            a3 = TonalSpec(HueAdd(60.0), ChromaBound(ChromaSource(), 70.0, Chroma.MAX_VALUE)),
-
-            // Not Used
-            n1 = TonalSpec(HueSource(), ChromaConstant(0.0)),
-            n2 = TonalSpec(HueSource(), ChromaConstant(0.0))
-        )
-    )
+enum class Style{
+    SPRITZ,
+    TONAL_SPOT,
+    VIBRANT,
+    EXPRESSIVE,
+    RAINBOW,
+    FRUIT_SALAD,
+    CONTENT,
+    MONOCHROMATIC,
+    CLOCK,
+    CLOCK_VIBRANT
 }
 
 class TonalPalette
 internal constructor(
-    private val spec: TonalSpec,
-    seedColor: Int,
+    private val materialTonalPalette: com.google.ux.material.libmonet.palettes.TonalPalette
 ) {
-    val seedCam: Cam = Cam.fromInt(seedColor)
-    val allShades: List<Int> = spec.shades(seedCam)
-    val allShadesMapped: Map<Int, Int> = SHADE_KEYS.zip(allShades).toMap()
-    val baseColor: Int
+    @Deprecated("Do not use. For color system only")
+    val allShades: List<Int>
+    val allShadesMapped: Map<Int, Int>
 
-    init {
-        val h = spec.hue.get(seedCam).toFloat()
-        val c = spec.chroma.get(seedCam).toFloat()
-        baseColor = ColorUtils.CAMToColor(h, c, CamUtils.lstarFromInt(seedColor))
+    init{
+        allShades = SHADE_KEYS.map {key -> getAtTone(key.toFloat()) }
+        allShadesMapped = SHADE_KEYS.zip(allShades).toMap()
     }
 
     // Dynamically computed tones across the full range from 0 to 1000
-    fun getAtTone(tone: Float) = spec.getAtTone(seedCam, tone)
+    fun getAtTone(shade: Float): Int = materialTonalPalette.tone(((1000.0f - shade) / 10f).toInt())
 
     // Predefined & precomputed tones
-    val s10: Int
+    val s0: Int
         get() = this.allShades[0]
-    val s50: Int
+    val s10: Int
         get() = this.allShades[1]
-    val s100: Int
+    val s50: Int
         get() = this.allShades[2]
-    val s200: Int
+    val s100: Int
         get() = this.allShades[3]
-    val s300: Int
+    val s200: Int
         get() = this.allShades[4]
-    val s400: Int
+    val s300: Int
         get() = this.allShades[5]
-    val s500: Int
+    val s400: Int
         get() = this.allShades[6]
-    val s600: Int
+    val s500: Int
         get() = this.allShades[7]
-    val s700: Int
+    val s600: Int
         get() = this.allShades[8]
-    val s800: Int
+    val s700: Int
         get() = this.allShades[9]
-    val s900: Int
+    val s800: Int
         get() = this.allShades[10]
-    val s1000: Int
+    val s900: Int
         get() = this.allShades[11]
+    val s1000: Int
+        get() = this.allShades[12]
 
     companion object {
-        val SHADE_KEYS = listOf(10, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000)
+        val SHADE_KEYS = listOf(0, 10, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000)
     }
 }
 
@@ -381,9 +105,20 @@
         "instead")
 class ColorScheme(
     @ColorInt val seed: Int,
-    val darkTheme: Boolean,
-    val style: Style = Style.TONAL_SPOT
+    val isDark: Boolean,
+    val style: Style,
+    val contrastLevel: Double
 ) {
+    var materialScheme: DynamicScheme
+
+    private val proposedSeedHct: Hct = Hct.fromInt(seed)
+    private val seedHct: Hct = Hct.fromInt(if (seed == Color.TRANSPARENT) {
+        GOOGLE_BLUE
+    } else if (style != Style.CONTENT && proposedSeedHct.chroma < 5) {
+        GOOGLE_BLUE
+    } else {
+        seed
+    })
 
     val accent1: TonalPalette
     val accent2: TonalPalette
@@ -395,62 +130,49 @@
 
     @JvmOverloads
     constructor(
+        @ColorInt seed: Int,
+        darkTheme: Boolean,
+        style: Style
+    ) : this(seed, darkTheme, style, 0.5)
+
+    @JvmOverloads
+    constructor(
         wallpaperColors: WallpaperColors,
         darkTheme: Boolean,
         style: Style = Style.TONAL_SPOT
     ) : this(getSeedColor(wallpaperColors, style != Style.CONTENT), darkTheme, style)
 
-    val allHues: List<TonalPalette>
-        get() {
-            return listOf(accent1, accent2, accent3, neutral1, neutral2)
-        }
-
-    val allAccentColors: List<Int>
-        get() {
-            val allColors = mutableListOf<Int>()
-            allColors.addAll(accent1.allShades)
-            allColors.addAll(accent2.allShades)
-            allColors.addAll(accent3.allShades)
-            return allColors
-        }
-
-    val allNeutralColors: List<Int>
-        get() {
-            val allColors = mutableListOf<Int>()
-            allColors.addAll(neutral1.allShades)
-            allColors.addAll(neutral2.allShades)
-            return allColors
-        }
-
     val backgroundColor
-        get() = ColorUtils.setAlphaComponent(if (darkTheme) neutral1.s700 else neutral1.s10, 0xFF)
+        get() = ColorUtils.setAlphaComponent(if (isDark) neutral1.s700 else neutral1.s10, 0xFF)
 
     val accentColor
-        get() = ColorUtils.setAlphaComponent(if (darkTheme) accent1.s100 else accent1.s500, 0xFF)
+        get() = ColorUtils.setAlphaComponent(if (isDark) accent1.s100 else accent1.s500, 0xFF)
 
     init {
-        val proposedSeedCam = Cam.fromInt(seed)
-        val seedArgb =
-            if (seed == Color.TRANSPARENT) {
-                GOOGLE_BLUE
-            } else if (style != Style.CONTENT && proposedSeedCam.chroma < 5) {
-                GOOGLE_BLUE
-            } else {
-                seed
-            }
+        materialScheme = when (style) {
+            Style.SPRITZ -> SchemeNeutral(seedHct, isDark, contrastLevel)
+            Style.TONAL_SPOT -> SchemeTonalSpot(seedHct, isDark, contrastLevel)
+            Style.VIBRANT -> SchemeVibrant(seedHct, isDark, contrastLevel)
+            Style.EXPRESSIVE -> SchemeExpressive(seedHct, isDark, contrastLevel)
+            Style.RAINBOW -> SchemeRainbow(seedHct, isDark, contrastLevel)
+            Style.FRUIT_SALAD -> SchemeFruitSalad(seedHct, isDark, contrastLevel)
+            Style.CONTENT -> SchemeContent(seedHct, isDark, contrastLevel)
+            Style.MONOCHROMATIC -> SchemeMonochrome(seedHct, isDark, contrastLevel)
 
-        accent1 = TonalPalette(style.coreSpec.a1, seedArgb)
-        accent2 = TonalPalette(style.coreSpec.a2, seedArgb)
-        accent3 = TonalPalette(style.coreSpec.a3, seedArgb)
-        neutral1 = TonalPalette(style.coreSpec.n1, seedArgb)
-        neutral2 = TonalPalette(style.coreSpec.n2, seedArgb)
+            // SystemUI Schemes
+            Style.CLOCK -> SchemeClock(seedHct, isDark, contrastLevel)
+            Style.CLOCK_VIBRANT -> SchemeClockVibrant(seedHct, isDark, contrastLevel)
+        }
+
+        accent1 = TonalPalette(materialScheme.primaryPalette)
+        accent2 = TonalPalette(materialScheme.secondaryPalette)
+        accent3 = TonalPalette(materialScheme.tertiaryPalette)
+        neutral1 = TonalPalette(materialScheme.neutralPalette)
+        neutral2 = TonalPalette(materialScheme.neutralVariantPalette)
     }
 
-    val shadeCount
-        get() = this.accent1.allShades.size
-
     val seedTone: Float
-        get() = 1000f - CamUtils.lstarFromInt(seed) * 10f
+        get() = 1000f - proposedSeedHct.tone.toFloat() * 10f
 
     override fun toString(): String {
         return "ColorScheme {\n" +
@@ -507,7 +229,7 @@
                             if (!filter) {
                                 true
                             } else {
-                                Cam.fromInt(it).chroma >= MIN_CHROMA
+                                Hct.fromInt(it).chroma >= MIN_CHROMA
                             }
                         }
                         .toList()
@@ -519,15 +241,15 @@
 
             val intToProportion =
                 wallpaperColors.allColors.mapValues { it.value.toDouble() / totalPopulation }
-            val intToCam = wallpaperColors.allColors.mapValues { Cam.fromInt(it.key) }
+            val intToHct = wallpaperColors.allColors.mapValues { Hct.fromInt(it.key) }
 
             // Get an array with 360 slots. A slot contains the percentage of colors with that hue.
-            val hueProportions = huePopulations(intToCam, intToProportion, filter)
+            val hueProportions = huePopulations(intToHct, intToProportion, filter)
             // Map each color to the percentage of the image with its hue.
             val intToHueProportion =
                 wallpaperColors.allColors.mapValues {
-                    val cam = intToCam[it.key]!!
-                    val hue = cam.hue.roundToInt()
+                    val hct = intToHct[it.key]!!
+                    val hue = hct.hue.roundToInt()
                     var proportion = 0.0
                     for (i in hue - 15..hue + 15) {
                         proportion += hueProportions[wrapDegrees(i)]
@@ -537,18 +259,18 @@
             // Remove any inappropriate seed colors. For example, low chroma colors look grayscale
             // raising their chroma will turn them to a much louder color that may not have been
             // in the image.
-            val filteredIntToCam =
-                if (!filter) intToCam
+            val filteredIntToHct =
+                if (!filter) intToHct
                 else
-                    (intToCam.filter {
-                        val cam = it.value
+                    (intToHct.filter {
+                        val hct = it.value
                         val proportion = intToHueProportion[it.key]!!
-                        cam.chroma >= MIN_CHROMA &&
+                        hct.chroma >= MIN_CHROMA &&
                             (totalPopulationMeaningless || proportion > 0.01)
                     })
             // Sort the colors by score, from high to low.
             val intToScoreIntermediate =
-                filteredIntToCam.mapValues { score(it.value, intToHueProportion[it.key]!!) }
+                filteredIntToHct.mapValues { score(it.value, intToHueProportion[it.key]!!) }
             val intToScore = intToScoreIntermediate.entries.toMutableList()
             intToScore.sortByDescending { it.value }
 
@@ -564,8 +286,8 @@
                     val int = entry.key
                     val existingSeedNearby =
                         seeds.find {
-                            val hueA = intToCam[int]!!.hue
-                            val hueB = intToCam[it]!!.hue
+                            val hueA = intToHct[int]!!.hue
+                            val hueB = intToHct[it]!!.hue
                             hueDiff(hueA, hueB) < i
                         } != null
                     if (existingSeedNearby) {
@@ -600,30 +322,16 @@
             }
         }
 
-        public fun wrapDegreesDouble(degrees: Double): Double {
-            return when {
-                degrees < 0 -> {
-                    (degrees % 360) + 360
-                }
-                degrees >= 360 -> {
-                    degrees % 360
-                }
-                else -> {
-                    degrees
-                }
-            }
-        }
-
-        private fun hueDiff(a: Float, b: Float): Float {
+        private fun hueDiff(a: Double, b: Double): Double {
             return 180f - ((a - b).absoluteValue - 180f).absoluteValue
         }
 
         private fun stringForColor(color: Int): String {
             val width = 4
-            val hct = Cam.fromInt(color)
+            val hct = Hct.fromInt(color)
             val h = "H${hct.hue.roundToInt().toString().padEnd(width)}"
             val c = "C${hct.chroma.roundToInt().toString().padEnd(width)}"
-            val t = "T${CamUtils.lstarFromInt(color).roundToInt().toString().padEnd(width)}"
+            val t = "T${hct.tone.roundToInt().toString().padEnd(width)}"
             val hex = Integer.toHexString(color and 0xffffff).padStart(6, '0').uppercase()
             return "$h$c$t = #$hex"
         }
@@ -633,16 +341,16 @@
                 colors.map { stringForColor(it) }.joinToString(separator = "\n") { it }
         }
 
-        private fun score(cam: Cam, proportion: Double): Double {
+        private fun score(hct: Hct, proportion: Double): Double {
             val proportionScore = 0.7 * 100.0 * proportion
             val chromaScore =
-                if (cam.chroma < ACCENT1_CHROMA) 0.1 * (cam.chroma - ACCENT1_CHROMA)
-                else 0.3 * (cam.chroma - ACCENT1_CHROMA)
+                if (hct.chroma < ACCENT1_CHROMA) 0.1 * (hct.chroma - ACCENT1_CHROMA)
+                else 0.3 * (hct.chroma - ACCENT1_CHROMA)
             return chromaScore + proportionScore
         }
 
         private fun huePopulations(
-            camByColor: Map<Int, Cam>,
+            hctByColor: Map<Int, Hct>,
             populationByColor: Map<Int, Double>,
             filter: Boolean = true
         ): List<Double> {
@@ -650,9 +358,9 @@
 
             for (entry in populationByColor.entries) {
                 val population = populationByColor[entry.key]!!
-                val cam = camByColor[entry.key]!!
-                val hue = cam.hue.roundToInt() % 360
-                if (filter && cam.chroma <= MIN_CHROMA) {
+                val hct = hctByColor[entry.key]!!
+                val hue = hct.hue.roundToInt() % 360
+                if (filter && hct.chroma <= MIN_CHROMA) {
                     continue
                 }
                 huePopulation[hue] = huePopulation[hue] + population
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/SchemeClock.java b/packages/SystemUI/monet/src/com/android/systemui/monet/SchemeClock.java
new file mode 100644
index 0000000..4747cc5
--- /dev/null
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/SchemeClock.java
@@ -0,0 +1,55 @@
+/*
+ * 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.monet;
+
+import static com.google.ux.material.libmonet.utils.MathUtils.clampDouble;
+
+import static java.lang.Double.max;
+
+import com.google.ux.material.libmonet.hct.Hct;
+import com.google.ux.material.libmonet.palettes.TonalPalette;
+import com.google.ux.material.libmonet.scheme.DynamicScheme;
+import com.google.ux.material.libmonet.scheme.Variant;
+
+public class SchemeClock extends DynamicScheme {
+    public SchemeClock(Hct sourceColorHct, boolean isDark, double contrastLevel) {
+        super(
+                sourceColorHct,
+                Variant.MONOCHROME,
+                isDark,
+                contrastLevel,
+                /*primary*/
+                TonalPalette.fromHueAndChroma(
+                        /*hue*/ sourceColorHct.getHue(),
+                        /*chroma*/ max(sourceColorHct.getChroma(), 20)
+                ),
+                /*secondary*/
+                TonalPalette.fromHueAndChroma(
+                        /*hue*/ sourceColorHct.getHue() + 10.0,
+                        /*chroma*/ clampDouble(17, 40, sourceColorHct.getChroma() * 0.85)
+                ),
+                /*tertiary*/
+                TonalPalette.fromHueAndChroma(
+                        /*hue*/ sourceColorHct.getHue() + 20.0,
+                        /*chroma*/ max(sourceColorHct.getChroma() + 20, 50)
+                ),
+
+                //not used
+                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 0.0),
+                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 0.0));
+    }
+}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/SchemeClockVibrant.java b/packages/SystemUI/monet/src/com/android/systemui/monet/SchemeClockVibrant.java
new file mode 100644
index 0000000..fb5e972
--- /dev/null
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/SchemeClockVibrant.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.monet;
+
+import static java.lang.Double.max;
+
+import com.google.ux.material.libmonet.hct.Hct;
+import com.google.ux.material.libmonet.palettes.TonalPalette;
+import com.google.ux.material.libmonet.scheme.DynamicScheme;
+import com.google.ux.material.libmonet.scheme.Variant;
+
+public class SchemeClockVibrant extends DynamicScheme {
+    public SchemeClockVibrant(Hct sourceColorHct, boolean isDark, double contrastLevel) {
+        super(
+                sourceColorHct,
+                Variant.MONOCHROME,
+                isDark,
+                contrastLevel,
+                /*primary*/
+                TonalPalette.fromHueAndChroma(
+                        /*hue*/ sourceColorHct.getHue(),
+                        /*chroma*/ max(sourceColorHct.getChroma(), 70)
+                ),
+                /*secondary*/
+                TonalPalette.fromHueAndChroma(
+                        /*hue*/ sourceColorHct.getHue() + 20.0,
+                        /*chroma*/ max(sourceColorHct.getChroma(), 70)
+                ),
+                /*tertiary*/
+                TonalPalette.fromHueAndChroma(
+                        /*hue*/ sourceColorHct.getHue() + 60.0,
+                        /*chroma*/ max(sourceColorHct.getChroma(), 70)
+                ),
+
+                //not used
+                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 0.0),
+                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 0.0));
+    }
+}
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 d624bf7..43266bf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -256,7 +256,7 @@
         }
 
     @Test
-    fun hubTimeout_whenDreaming() =
+    fun hubTimeout_whenDreaming_goesToBlank() =
         with(kosmos) {
             testScope.runTest {
                 // Device is dreaming and on communal.
@@ -273,7 +273,22 @@
         }
 
     @Test
-    fun hubTimeout_dreamStopped() =
+    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.
@@ -296,6 +311,28 @@
         }
 
     @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 {
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..fc604aa 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
@@ -330,6 +348,48 @@
         }
 
     @Test
+    fun alpha_shadeClosedOverLockscreen_isOne() =
+        testScope.runTest {
+            val alpha by collectLastValue(underTest.alpha(viewState))
+
+            // Transition to the lockscreen.
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.AOD,
+                to = KeyguardState.LOCKSCREEN,
+                testScope,
+            )
+
+            // Open the shade.
+            shadeRepository.setQsExpansion(1f)
+            assertThat(alpha).isEqualTo(0f)
+
+            // Close the shade, alpha returns to 1.
+            shadeRepository.setQsExpansion(0f)
+            assertThat(alpha).isEqualTo(1f)
+        }
+
+    @Test
+    fun alpha_shadeClosedOverDream_isZero() =
+        testScope.runTest {
+            val alpha by collectLastValue(underTest.alpha(viewState))
+
+            // Transition to dreaming.
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.DREAMING,
+                testScope,
+            )
+
+            // Open the shade.
+            shadeRepository.setQsExpansion(1f)
+            assertThat(alpha).isEqualTo(0f)
+
+            // Close the shade, alpha is still 0 since we're not on the lockscreen.
+            shadeRepository.setQsExpansion(0f)
+            assertThat(alpha).isEqualTo(0f)
+        }
+
+    @Test
     fun alpha_idleOnOccluded_isZero() =
         testScope.runTest {
             val alpha by collectLastValue(underTest.alpha(viewState))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
index ad1cef1..751ac1d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
@@ -26,7 +26,8 @@
 import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.res.R
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
@@ -47,7 +48,7 @@
     fun setup() {
         with(kosmos) {
             fakeFeatureFlagsClassic.set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, true)
-            overrideResource(R.bool.config_use_split_notification_shade, false)
+            shadeRepository.setShadeMode(ShadeMode.Single)
             underTest = lockscreenContentViewModel
         }
     }
@@ -92,10 +93,10 @@
     fun areNotificationsVisible_splitShadeTrue_true() =
         with(kosmos) {
             testScope.runTest {
-                overrideResource(R.bool.config_use_split_notification_shade, true)
+                shadeRepository.setShadeMode(ShadeMode.Split)
                 kosmos.fakeKeyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE)
 
-                assertThat(underTest.areNotificationsVisible(context.resources)).isTrue()
+                assertThat(underTest.areNotificationsVisible).isTrue()
             }
         }
     @Test
@@ -103,7 +104,7 @@
         with(kosmos) {
             testScope.runTest {
                 kosmos.fakeKeyguardClockRepository.setClockSize(KeyguardClockSwitch.SMALL)
-                assertThat(underTest.areNotificationsVisible(context.resources)).isTrue()
+                assertThat(underTest.areNotificationsVisible).isTrue()
             }
         }
 
@@ -112,7 +113,34 @@
         with(kosmos) {
             testScope.runTest {
                 kosmos.fakeKeyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE)
-                assertThat(underTest.areNotificationsVisible(context.resources)).isFalse()
+                assertThat(underTest.areNotificationsVisible).isFalse()
+            }
+        }
+
+    @Test
+    fun shouldUseSplitNotificationShade_withConfigTrue_true() =
+        with(kosmos) {
+            testScope.runTest {
+                shadeRepository.setShadeMode(ShadeMode.Split)
+                assertThat(underTest.shouldUseSplitNotificationShade).isTrue()
+            }
+        }
+
+    @Test
+    fun shouldUseSplitNotificationShade_withConfigFalse_false() =
+        with(kosmos) {
+            testScope.runTest {
+                shadeRepository.setShadeMode(ShadeMode.Single)
+                assertThat(underTest.shouldUseSplitNotificationShade).isFalse()
+            }
+        }
+
+    @Test
+    fun sceneKey() =
+        with(kosmos) {
+            testScope.runTest {
+                shadeRepository.setShadeMode(ShadeMode.Single)
+                assertThat(underTest.shouldUseSplitNotificationShade).isFalse()
             }
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 278bf9b..53a8e5d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.common.shared.model.NotificationContainerBounds
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -721,39 +722,36 @@
     @Test
     fun shadeCollapseFadeIn() =
         testScope.runTest {
-            val fadeIn by collectLastValue(underTest.shadeCollapseFadeIn)
+            val fadeIn by collectValues(underTest.shadeCollapseFadeIn)
 
             // Start on lockscreen without the shade
-            underTest.setShadeCollapseFadeInComplete(false)
             showLockscreen()
-            assertThat(fadeIn).isEqualTo(false)
+            assertThat(fadeIn[0]).isEqualTo(false)
 
             // ... then the shade expands
             showLockscreenWithShadeExpanded()
-            assertThat(fadeIn).isEqualTo(false)
+            assertThat(fadeIn[0]).isEqualTo(false)
 
             // ... it collapses
             showLockscreen()
-            assertThat(fadeIn).isEqualTo(true)
+            assertThat(fadeIn[1]).isEqualTo(true)
 
-            // ... now send animation complete signal
-            underTest.setShadeCollapseFadeInComplete(true)
-            assertThat(fadeIn).isEqualTo(false)
+            // ... and ensure the value goes back to false
+            assertThat(fadeIn[2]).isEqualTo(false)
         }
 
     @Test
     fun shadeCollapseFadeIn_doesNotRunIfTransitioningToAod() =
         testScope.runTest {
-            val fadeIn by collectLastValue(underTest.shadeCollapseFadeIn)
+            val fadeIn by collectValues(underTest.shadeCollapseFadeIn)
 
             // Start on lockscreen without the shade
-            underTest.setShadeCollapseFadeInComplete(false)
             showLockscreen()
-            assertThat(fadeIn).isEqualTo(false)
+            assertThat(fadeIn[0]).isEqualTo(false)
 
             // ... then the shade expands
             showLockscreenWithShadeExpanded()
-            assertThat(fadeIn).isEqualTo(false)
+            assertThat(fadeIn[0]).isEqualTo(false)
 
             // ... then user hits power to go to AOD
             keyguardTransitionRepository.sendTransitionSteps(
@@ -764,7 +762,7 @@
             // ... followed by a shade collapse
             showLockscreen()
             // ... does not trigger a fade in
-            assertThat(fadeIn).isEqualTo(false)
+            assertThat(fadeIn[0]).isEqualTo(false)
         }
 
     private suspend fun TestScope.showLockscreen() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
index 183a58a..be63301 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
@@ -25,6 +25,7 @@
 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.statusbar.policy.HeadsUpManagerTestUtil.createFullScreenIntentEntry
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.settings.FakeGlobalSettings
 import com.android.systemui.util.time.FakeSystemClock
@@ -97,6 +98,12 @@
         return entry
     }
 
+    private fun createFsiHeadsUpEntry(id: Int): BaseHeadsUpManager.HeadsUpEntry {
+        val entry = testableHeadsUpManager!!.createHeadsUpEntry()
+        entry.setEntry(createFullScreenIntentEntry(id, mContext))
+        return entry
+    }
+
     @Test
     fun testUpdate_isShowing_runsRunnable() {
         // Entry is showing
@@ -238,4 +245,68 @@
         // Next entry is shown
         Truth.assertThat(mAvalancheController.headsUpEntryShowing).isEqualTo(nextEntry)
     }
+
+    @Test
+    fun testGetDurationMs_lastEntry_useAutoDismissTime() {
+        // Entry is showing
+        val showingEntry = createHeadsUpEntry(id = 0)
+        mAvalancheController.headsUpEntryShowing = showingEntry
+
+        // Nothing is next
+        mAvalancheController.clearNext()
+
+        val durationMs = mAvalancheController.getDurationMs(showingEntry, autoDismissMs = 5000)
+        Truth.assertThat(durationMs).isEqualTo(5000)
+    }
+
+    @Test
+    fun testGetDurationMs_nextEntryLowerPriority_500() {
+        // Entry is showing
+        val showingEntry = createFsiHeadsUpEntry(id = 1)
+        mAvalancheController.headsUpEntryShowing = showingEntry
+
+        // There's another entry waiting to show next
+        val nextEntry = createHeadsUpEntry(id = 0)
+        mAvalancheController.addToNext(nextEntry, runnableMock!!)
+
+        // Next entry has lower priority
+        Truth.assertThat(nextEntry.compareNonTimeFields(showingEntry)).isEqualTo(1)
+
+        val durationMs = mAvalancheController.getDurationMs(showingEntry, autoDismissMs = 5000)
+        Truth.assertThat(durationMs).isEqualTo(5000)
+    }
+
+    @Test
+    fun testGetDurationMs_nextEntrySamePriority_1000() {
+        // 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!!)
+
+        // Same priority
+        Truth.assertThat(nextEntry.compareNonTimeFields(showingEntry)).isEqualTo(0)
+
+        val durationMs = mAvalancheController.getDurationMs(showingEntry, autoDismissMs = 5000)
+        Truth.assertThat(durationMs).isEqualTo(1000)
+    }
+
+    @Test
+    fun testGetDurationMs_nextEntryHigherPriority_500() {
+        // Entry is showing
+        val showingEntry = createHeadsUpEntry(id = 0)
+        mAvalancheController.headsUpEntryShowing = showingEntry
+
+        // There's another entry waiting to show next
+        val nextEntry = createFsiHeadsUpEntry(id = 1)
+        mAvalancheController.addToNext(nextEntry, runnableMock!!)
+
+        // Next entry has higher priority
+        Truth.assertThat(nextEntry.compareNonTimeFields(showingEntry)).isEqualTo(-1)
+
+        val durationMs = mAvalancheController.getDurationMs(showingEntry, autoDismissMs = 5000)
+        Truth.assertThat(durationMs).isEqualTo(500)
+    }
 }
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 830bcef..ed0d272 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
@@ -117,21 +117,6 @@
         return HeadsUpManagerTestUtil.createEntry(id, notif);
     }
 
-    private PendingIntent createFullScreenIntent() {
-        return PendingIntent.getActivity(
-                getContext(), 0, new Intent(getContext(), this.getClass()),
-                PendingIntent.FLAG_MUTABLE_UNAUDITED);
-    }
-
-    private NotificationEntry createFullScreenIntentEntry(int id) {
-        final Notification notif = new Notification.Builder(mContext, "")
-                .setSmallIcon(R.drawable.ic_person)
-                .setFullScreenIntent(createFullScreenIntent(), /* highPriority */ true)
-                .build();
-        return HeadsUpManagerTestUtil.createEntry(id, notif);
-    }
-
-
     private void useAccessibilityTimeout(boolean use) {
         if (use) {
             doReturn(TEST_A11Y_AUTO_DISMISS_TIME).when(mAccessibilityMgr)
@@ -239,7 +224,8 @@
     @Test
     public void testShouldHeadsUpBecomePinned_hasFSI_notUnpinned_true() {
         final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry notifEntry = createFullScreenIntentEntry(/* id = */ 0);
+        final NotificationEntry notifEntry =
+                HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext);
 
         // Add notifEntry to ANM mAlertEntries map and make it NOT unpinned
         hum.showNotification(notifEntry);
@@ -254,7 +240,8 @@
     @Test
     public void testShouldHeadsUpBecomePinned_wasUnpinned_false() {
         final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry notifEntry = createFullScreenIntentEntry(/* id = */ 0);
+        final NotificationEntry notifEntry =
+                HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext);
 
         // Add notifEntry to ANM mAlertEntries map and make it unpinned
         hum.showNotification(notifEntry);
@@ -443,7 +430,8 @@
     @Test
     public void testIsSticky_hasFullScreenIntent_true() {
         final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry notifEntry = createFullScreenIntentEntry(/* id = */ 0);
+        final NotificationEntry notifEntry =
+                HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext);
 
         hum.showNotification(notifEntry);
 
@@ -554,7 +542,8 @@
 
         // Needs full screen intent in order to be pinned
         final BaseHeadsUpManager.HeadsUpEntry entryToPin = hum.new HeadsUpEntry();
-        entryToPin.setEntry(createFullScreenIntentEntry(/* id = */ 0));
+        entryToPin.setEntry(
+                HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext));
 
         // Note: the standard way to show a notification would be calling showNotification rather
         // than onAlertEntryAdded. However, in practice showNotification in effect adds
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTestUtil.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTestUtil.java
index c70b03b..bda8619 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTestUtil.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTestUtil.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.policy;
 import android.app.ActivityManager;
+import android.app.PendingIntent;
+import android.content.Intent;
 import android.os.UserHandle;
 
 import android.content.Context;
@@ -67,4 +69,16 @@
         return new NotificationEntryBuilder().setSbn(
                 HeadsUpManagerTestUtil.createSbn(id, context)).build();
     }
+
+    protected static NotificationEntry createFullScreenIntentEntry(int id, Context context) {
+        final PendingIntent intent = PendingIntent.getActivity(
+                context, 0, new Intent(),
+                PendingIntent.FLAG_IMMUTABLE);
+
+        final Notification notif = new Notification.Builder(context, "")
+                .setSmallIcon(com.android.systemui.res.R.drawable.ic_person)
+                .setFullScreenIntent(intent, /* highPriority */ true)
+                .build();
+        return HeadsUpManagerTestUtil.createEntry(id, notif);
+    }
 }
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/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..2285550 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>
@@ -1967,4 +1968,7 @@
     <!-- Microns/ums (1000 um = 1mm) per pixel for the given device. If unspecified, UI that
          relies on this value will not be sized correctly. -->
     <item name="pixel_pitch" format="float" type="dimen">-1</item>
+
+    <!-- SliceView grid gutter for ANC Slice -->
+    <dimen name="abc_slice_grid_gutter">0dp</dimen>
 </resources>
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/res/xml/home_controls_dream_metadata.xml b/packages/SystemUI/res/xml/home_controls_dream_metadata.xml
index eb7c79e..69bd9a4 100644
--- a/packages/SystemUI/res/xml/home_controls_dream_metadata.xml
+++ b/packages/SystemUI/res/xml/home_controls_dream_metadata.xml
@@ -16,4 +16,5 @@
 <dream xmlns:android="http://schemas.android.com/apk/res/android"
        android:showClockAndComplications="false"
        android:previewImage="@drawable/homecontrols_sq"
+       android:dreamCategory="home_panel"
     />
\ No newline at end of file
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/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index da49201..c6716e4 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -54,7 +54,7 @@
 import com.android.systemui.settings.DisplayTracker;
 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.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
@@ -190,7 +190,7 @@
     private final NotificationShadeWindowController mNotificationShadeController;
     private final KeyguardStateController mKeyguardStateController;
     private final ShadeController mShadeController;
-    private final Lazy<ShadeViewController> mShadeViewController;
+    private final Lazy<PanelExpansionInteractor> mPanelExpansionInteractor;
     private final StatusBarWindowCallback mNotificationShadeCallback;
     private boolean mDismissNotificationShadeActionRegistered;
 
@@ -200,14 +200,14 @@
             NotificationShadeWindowController notificationShadeController,
             KeyguardStateController keyguardStateController,
             ShadeController shadeController,
-            Lazy<ShadeViewController> shadeViewController,
+            Lazy<PanelExpansionInteractor> panelExpansionInteractor,
             Optional<Recents> recentsOptional,
             DisplayTracker displayTracker) {
         mContext = context;
         mUserTracker = userTracker;
         mKeyguardStateController = keyguardStateController;
         mShadeController = shadeController;
-        mShadeViewController = shadeViewController;
+        mPanelExpansionInteractor = panelExpansionInteractor;
         mRecentsOptional = recentsOptional;
         mDisplayTracker = displayTracker;
         mReceiver = new SystemActionsBroadcastReceiver();
@@ -330,7 +330,7 @@
     private void registerOrUnregisterDismissNotificationShadeAction() {
         Assert.isMainThread();
 
-        if (mShadeViewController.get().isPanelExpanded()
+        if (mPanelExpansionInteractor.get().isPanelExpanded()
                 && !mKeyguardStateController.isShowing()) {
             if (!mDismissNotificationShadeActionRegistered) {
                 mA11yManager.registerSystemAction(
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
index 9dd1454..0f4d63c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
@@ -302,6 +302,14 @@
         if (mEndAnimationCanceled || mController == null) {
             return;
         }
+
+        // If the animation is playing backwards, mStartSpec will be the final spec we would
+        // like to reach.
+        AnimationSpec spec = isReverse ? mStartSpec : mEndSpec;
+        mController.enableWindowMagnificationInternal(
+                spec.mScale, spec.mCenterX, spec.mCenterY,
+                mMagnificationFrameOffsetRatioX, mMagnificationFrameOffsetRatioY);
+
         if (mState == STATE_DISABLING) {
             mController.deleteWindowMagnification();
         }
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 270fedc..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;
@@ -434,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;
@@ -464,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/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 97e38f4..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(
@@ -488,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();
@@ -497,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/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 8bd675c..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);
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 e457601..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,10 +78,8 @@
                     }
 
                 launch {
-                    var width = 0
-                    var height = 0
-                    combine(promptViewModel.size, viewModel.activeAuthType, ::Pair).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
@@ -109,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.
@@ -122,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/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 86b0b44..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
@@ -86,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) {
@@ -96,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 */
@@ -342,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/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index 98c8205..ef686f9 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -30,6 +30,7 @@
 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
@@ -38,7 +39,6 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.launchIn
@@ -108,23 +108,29 @@
         bgScope.launch {
             combine(
                     communalInteractor.desiredScene,
-                    keyguardInteractor.isDreaming,
                     // Emit a value on start so the combine starts.
                     communalInteractor.userActivity.emitOnStart()
-                ) { scene, isDreaming, _ ->
+                ) { scene, _ ->
                     // Time out should run whenever we're dreaming and the hub is open, even if not
                     // docked.
-                    scene == CommunalScenes.Communal && isDreaming
+                    scene == CommunalScenes.Communal
                 }
-                // collectLatest cancels the previous action block when new values arrive, so any
+                // 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.
-                .collectLatest { shouldTimeout ->
+                .mapLatest { shouldTimeout ->
                     if (!shouldTimeout) {
-                        return@collectLatest
+                        return@mapLatest false
                     }
+
                     delay(screenTimeout.milliseconds)
-                    communalInteractor.onSceneChanged(CommunalScenes.Blank)
+                    true
+                }
+                .sample(keyguardInteractor.isDreaming, ::Pair)
+                .collect { (shouldTimeout, isDreaming) ->
+                    if (isDreaming && shouldTimeout) {
+                        communalInteractor.onSceneChanged(CommunalScenes.Blank)
+                    }
                 }
         }
     }
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/DefaultBroadcastReceiverBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
index 3d8e4cb..6aa5e8b 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
@@ -22,8 +22,6 @@
 import com.android.systemui.media.dialog.MediaOutputDialogReceiver;
 import com.android.systemui.people.widget.PeopleSpaceWidgetPinnedReceiver;
 import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
-import com.android.systemui.screenshot.ActionProxyReceiver;
-import com.android.systemui.screenshot.DeleteScreenshotReceiver;
 import com.android.systemui.screenshot.SmartActionsReceiver;
 import com.android.systemui.volume.VolumePanelDialogReceiver;
 
@@ -42,24 +40,6 @@
      */
     @Binds
     @IntoMap
-    @ClassKey(ActionProxyReceiver.class)
-    public abstract BroadcastReceiver bindActionProxyReceiver(
-            ActionProxyReceiver broadcastReceiver);
-
-    /**
-     *
-     */
-    @Binds
-    @IntoMap
-    @ClassKey(DeleteScreenshotReceiver.class)
-    public abstract BroadcastReceiver bindDeleteScreenshotReceiver(
-            DeleteScreenshotReceiver broadcastReceiver);
-
-    /**
-     *
-     */
-    @Binds
-    @IntoMap
     @ClassKey(SmartActionsReceiver.class)
     public abstract BroadcastReceiver bindSmartActionsReceiver(
             SmartActionsReceiver broadcastReceiver);
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/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/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/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index 88ddfd4..47f8046a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -33,7 +33,11 @@
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
 
 @SysUISingleton
@@ -66,6 +70,25 @@
         listenForTransitionToCamera(scope, keyguardInteractor)
     }
 
+    val surfaceBehindVisibility: Flow<Boolean?> =
+        combine(
+                transitionInteractor.startedKeyguardTransitionStep,
+                transitionInteractor.transitionStepsFromState(KeyguardState.ALTERNATE_BOUNCER)
+            ) { startedStep, fromBouncerStep ->
+                if (startedStep.to != KeyguardState.GONE) {
+                    return@combine null
+                }
+
+                // The alt bouncer is pretty fast to hide, so start the surface behind animation
+                // around 30%.
+                fromBouncerStep.value > 0.3f
+            }
+            .onStart {
+                // Default to null ("don't care, use a reasonable default").
+                emit(null)
+            }
+            .distinctUntilChanged()
+
     private fun listenForAlternateBouncerToLockscreenHubAodOrDozing() {
         scope.launch {
             keyguardInteractor.alternateBouncerShowing
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/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index 391dccc..c7fafba 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -26,7 +26,6 @@
 import com.android.systemui.keyguard.KeyguardWmStateRefactor
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
@@ -96,36 +95,6 @@
             }
             .distinctUntilChanged()
 
-    val surfaceBehindModel: Flow<KeyguardSurfaceBehindModel?> =
-        combine(
-                transitionInteractor.startedKeyguardTransitionStep,
-                transitionInteractor.transitionStepsFromState(KeyguardState.PRIMARY_BOUNCER)
-            ) { startedStep, fromBouncerStep ->
-                if (startedStep.to != KeyguardState.GONE) {
-                    // BOUNCER to anything but GONE does not require any special surface
-                    // visibility handling.
-                    return@combine null
-                }
-
-                if (fromBouncerStep.value > 0.5f) {
-                    KeyguardSurfaceBehindModel(
-                        animateFromAlpha = 0f,
-                        alpha = 1f,
-                        animateFromTranslationY = 500f,
-                        translationY = 0f,
-                    )
-                } else {
-                    KeyguardSurfaceBehindModel(
-                        alpha = 0f,
-                    )
-                }
-            }
-            .onStart {
-                // Default to null ("don't care, use a reasonable default").
-                emit(null)
-            }
-            .distinctUntilChanged()
-
     fun dismissPrimaryBouncer() {
         scope.launch { startTransitionTo(KeyguardState.GONE) }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
index c877192..d39bd3d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
@@ -92,7 +92,7 @@
             when {
                 useWeatherClockLayout && useSplitShade -> SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID
                 useWeatherClockLayout -> WEATHER_CLOCK_BLUEPRINT_ID
-                useSplitShade -> SplitShadeKeyguardBlueprint.ID
+                useSplitShade && !ComposeLockscreen.isEnabled -> SplitShadeKeyguardBlueprint.ID
                 else -> DefaultKeyguardBlueprint.DEFAULT
             }
 
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/KeyguardSurfaceBehindInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
index c496a6e..80e94a2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
@@ -95,7 +95,17 @@
             }
             .distinctUntilChanged()
 
-    val isAnimatingSurface = repository.isAnimatingSurface
+    /**
+     * Whether we're animating the surface, or a notification launch animation is running (which
+     * means we're going to animate the surface, even if animators aren't yet running).
+     */
+    val isAnimatingSurface =
+        combine(
+            repository.isAnimatingSurface,
+            notificationLaunchInteractor.isLaunchAnimationRunning
+        ) { animatingSurface, animatingLaunch ->
+            animatingSurface || animatingLaunch
+        }
 
     fun setAnimatingSurface(animating: Boolean) {
         repository.setAnimatingSurface(animating)
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/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
index cff74b3..8d02e0e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -40,6 +40,7 @@
     surfaceBehindInteractor: KeyguardSurfaceBehindInteractor,
     fromLockscreenInteractor: FromLockscreenTransitionInteractor,
     fromBouncerInteractor: FromPrimaryBouncerTransitionInteractor,
+    fromAlternateBouncerInteractor: FromAlternateBouncerTransitionInteractor,
     notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor,
 ) {
     private val defaultSurfaceBehindVisibility =
@@ -65,6 +66,9 @@
                     KeyguardState.PRIMARY_BOUNCER -> {
                         fromBouncerInteractor.surfaceBehindVisibility
                     }
+                    KeyguardState.ALTERNATE_BOUNCER -> {
+                        fromAlternateBouncerInteractor.surfaceBehindVisibility
+                    }
                     else -> flowOf(null)
                 }
             }
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/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index 58c45c7..b6622e5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -28,8 +28,9 @@
 import com.android.systemui.keyguard.shared.model.SettingsClockSize
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
-import com.android.systemui.statusbar.policy.SplitShadeStateController
 import com.android.systemui.util.Utils
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -46,8 +47,8 @@
     keyguardInteractor: KeyguardInteractor,
     private val keyguardClockInteractor: KeyguardClockInteractor,
     @Application private val applicationScope: CoroutineScope,
-    private val splitShadeStateController: SplitShadeStateController,
     notifsKeyguardInteractor: NotificationsKeyguardInteractor,
+    private val shadeInteractor: ShadeInteractor,
 ) {
     var burnInLayer: Layer? = null
     val useLargeClock: Boolean
@@ -111,12 +112,33 @@
             initialValue = false
         )
 
+    val currentClockLayout: StateFlow<ClockLayout> =
+        combine(isLargeClockVisible, clockShouldBeCentered, shadeInteractor.shadeMode) {
+                isLargeClockVisible,
+                clockShouldBeCentered,
+                shadeMode ->
+                val shouldUseSplitShade = shadeMode == ShadeMode.Split
+                when {
+                    shouldUseSplitShade && clockShouldBeCentered -> ClockLayout.LARGE_CLOCK
+                    shouldUseSplitShade && isLargeClockVisible ->
+                        ClockLayout.SPLIT_SHADE_LARGE_CLOCK
+                    shouldUseSplitShade -> ClockLayout.SPLIT_SHADE_SMALL_CLOCK
+                    isLargeClockVisible -> ClockLayout.LARGE_CLOCK
+                    else -> ClockLayout.SMALL_CLOCK
+                }
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = ClockLayout.SMALL_CLOCK
+            )
+
     /** Calculates the top margin for the small clock. */
     fun getSmallClockTopMargin(context: Context): Int {
         var topMargin: Int
         val statusBarHeight = Utils.getStatusBarHeaderHeightKeyguard(context)
 
-        if (splitShadeStateController.shouldUseSplitNotificationShade(context.resources)) {
+        if (shadeInteractor.shadeMode.value == ShadeMode.Split) {
             topMargin =
                 context.resources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin)
             if (ComposeLockscreen.isEnabled) {
@@ -130,4 +152,11 @@
         }
         return topMargin
     }
+
+    enum class ClockLayout {
+        LARGE_CLOCK,
+        SMALL_CLOCK,
+        SPLIT_SHADE_LARGE_CLOCK,
+        SPLIT_SHADE_SMALL_CLOCK,
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 5ca9215..41cc1d6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -56,6 +56,7 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.combineTransform
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.filterNotNull
@@ -125,13 +126,36 @@
             .onStart { emit(false) }
             .distinctUntilChanged()
 
-    private val alphaOnShadeExpansion: Flow<Float> =
+    private val isOnLockscreen: Flow<Boolean> =
         combine(
+                keyguardTransitionInteractor.isFinishedInState(LOCKSCREEN).onStart { emit(false) },
+                keyguardTransitionInteractor
+                    .isInTransitionWhere { from, to -> from == LOCKSCREEN || to == LOCKSCREEN }
+                    .onStart { emit(false) }
+            ) { onLockscreen, transitioningToOrFromLockscreen ->
+                onLockscreen || transitioningToOrFromLockscreen
+            }
+            .distinctUntilChanged()
+
+    private val alphaOnShadeExpansion: Flow<Float> =
+        combineTransform(
+                isOnLockscreen,
                 shadeInteractor.qsExpansion,
                 shadeInteractor.shadeExpansion,
-            ) { qsExpansion, shadeExpansion ->
+            ) { isOnLockscreen, qsExpansion, shadeExpansion ->
                 // Fade out quickly as the shade expands
-                1f - MathUtils.constrainedMap(0f, 1f, 0f, 0.2f, max(qsExpansion, shadeExpansion))
+                if (isOnLockscreen) {
+                    val alpha =
+                        1f -
+                            MathUtils.constrainedMap(
+                                /* rangeMin = */ 0f,
+                                /* rangeMax = */ 1f,
+                                /* valueMin = */ 0f,
+                                /* valueMax = */ 0.2f,
+                                /* value = */ max(qsExpansion, shadeExpansion)
+                            )
+                    emit(alpha)
+                }
             }
             .distinctUntilChanged()
 
@@ -235,11 +259,7 @@
         burnInJob?.cancel()
 
         burnInJob =
-            scope.launch {
-                aodBurnInViewModel.movement(params).collect {
-                    burnInModel.value = it
-                }
-            }
+            scope.launch { aodBurnInViewModel.movement(params).collect { burnInModel.value = it } }
     }
 
     val scale: Flow<BurnInScaleViewModel> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index 23320be..1f80441 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -23,7 +23,8 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.res.R
-import com.android.systemui.statusbar.policy.SplitShadeStateController
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
@@ -40,7 +41,7 @@
     private val interactor: KeyguardBlueprintInteractor,
     private val authController: AuthController,
     val longPress: KeyguardLongPressViewModel,
-    val splitShadeStateController: SplitShadeStateController,
+    val shadeInteractor: ShadeInteractor,
 ) {
     private val clockSize = clockInteractor.clockSize
 
@@ -48,10 +49,12 @@
         get() = authController.isUdfpsSupported
     val isLargeClockVisible: Boolean
         get() = clockSize.value == KeyguardClockSwitch.LARGE
-    fun areNotificationsVisible(resources: Resources): Boolean {
-        return !isLargeClockVisible ||
-            splitShadeStateController.shouldUseSplitNotificationShade(resources)
-    }
+
+    val areNotificationsVisible: Boolean
+        get() = !isLargeClockVisible || shouldUseSplitNotificationShade
+
+    val shouldUseSplitNotificationShade: Boolean
+        get() = shadeInteractor.shadeMode.value == ShadeMode.Split
 
     fun getSmartSpacePaddingTop(resources: Resources): Int {
         return if (isLargeClockVisible) {
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/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/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index c657b55..9c88eb9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -17,6 +17,8 @@
 import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
+import static com.android.systemui.Flags.centralizedStatusBarHeightFix;
+
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -27,6 +29,7 @@
 import android.widget.FrameLayout;
 
 import com.android.systemui.res.R;
+import com.android.systemui.shade.LargeScreenHeaderHelper;
 import com.android.systemui.util.LargeScreenUtils;
 
 /**
@@ -97,7 +100,9 @@
             qqsLP.topMargin = mContext.getResources()
                     .getDimensionPixelSize(R.dimen.qqs_layout_margin_top);
         } else {
-            qqsLP.topMargin = mContext.getResources()
+            qqsLP.topMargin = centralizedStatusBarHeightFix()
+                    ? LargeScreenHeaderHelper.getLargeScreenHeaderHeight(mContext)
+                    : mContext.getResources()
                     .getDimensionPixelSize(R.dimen.large_screen_shade_header_min_height);
         }
         mHeaderQsPanel.setLayoutParams(qqsLP);
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/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
index bd66843..d82b175 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
@@ -41,6 +41,7 @@
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.recordissue.IssueRecordingService
 import com.android.systemui.recordissue.RecordIssueDialogDelegate
@@ -66,6 +67,7 @@
     private val keyguardDismissUtil: KeyguardDismissUtil,
     private val keyguardStateController: KeyguardStateController,
     private val dialogTransitionAnimator: DialogTransitionAnimator,
+    private val panelInteractor: PanelInteractor,
     private val userContextProvider: UserContextProvider,
     private val delegateFactory: RecordIssueDialogDelegate.Factory,
 ) :
@@ -138,6 +140,8 @@
                 .create {
                     isRecording = true
                     startIssueRecordingService(it.screenRecord, it.winscopeTracing)
+                    dialogTransitionAnimator.disableAllCurrentDialogsExitAnimations()
+                    panelInteractor.collapsePanels()
                     refreshState()
                 }
                 .createDialog()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt
index dcae088..1247854 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt
@@ -30,7 +30,7 @@
     private val bluetoothAutoOnRepository: BluetoothAutoOnRepository,
 ) {
 
-    val isEnabled = bluetoothAutoOnRepository.getValue.map { it == ENABLED }.distinctUntilChanged()
+    val isEnabled = bluetoothAutoOnRepository.isAutoOn.map { it == ENABLED }.distinctUntilChanged()
 
     /**
      * Checks if the auto on value is present in the repository.
@@ -49,7 +49,7 @@
             Log.e(TAG, "Trying to set toggle value while feature not available.")
         } else {
             val newValue = if (value) ENABLED else DISABLED
-            bluetoothAutoOnRepository.setValue(newValue)
+            bluetoothAutoOnRepository.setAutoOn(newValue)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt
index e17b4d3..f97fc38 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt
@@ -16,8 +16,6 @@
 
 package com.android.systemui.qs.tiles.dialog.bluetooth
 
-import android.os.UserHandle
-import android.util.Log
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
@@ -27,17 +25,21 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.withContext
 
-/** Repository class responsible for managing the Bluetooth Auto-On feature settings. */
-// TODO(b/316822488): Handle multi-user
+/**
+ * Repository class responsible for managing the Bluetooth Auto-On feature settings for the current
+ * user.
+ */
 @SysUISingleton
 class BluetoothAutoOnRepository
 @Inject
@@ -47,21 +49,24 @@
     @Application private val coroutineScope: CoroutineScope,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
 ) {
-    // Flow representing the auto on setting value
-    internal val getValue: Flow<Int> =
-        secureSettings
-            .observerFlow(UserHandle.USER_SYSTEM, SETTING_NAME)
-            .onStart { emit(Unit) }
-            .map {
-                if (userRepository.getSelectedUserInfo().id != UserHandle.USER_SYSTEM) {
-                    Log.i(TAG, "Current user is not USER_SYSTEM. Multi-user is not supported")
-                    return@map UNSET
-                }
-                secureSettings.getIntForUser(SETTING_NAME, UNSET, UserHandle.USER_SYSTEM)
+
+    // Flow representing the auto on setting value for the current user
+    @OptIn(ExperimentalCoroutinesApi::class)
+    internal val isAutoOn: StateFlow<Int> =
+        userRepository.selectedUserInfo
+            .flatMapLatest { userInfo ->
+                secureSettings
+                    .observerFlow(userInfo.id, SETTING_NAME)
+                    .onStart { emit(Unit) }
+                    .map { secureSettings.getIntForUser(SETTING_NAME, UNSET, userInfo.id) }
             }
             .distinctUntilChanged()
             .flowOn(backgroundDispatcher)
-            .shareIn(coroutineScope, SharingStarted.WhileSubscribed(replayExpirationMillis = 0))
+            .stateIn(
+                coroutineScope,
+                SharingStarted.WhileSubscribed(replayExpirationMillis = 0),
+                UNSET
+            )
 
     /**
      * Checks if the auto on setting value is ever set for the current user.
@@ -70,12 +75,11 @@
      */
     suspend fun isValuePresent(): Boolean =
         withContext(backgroundDispatcher) {
-            if (userRepository.getSelectedUserInfo().id != UserHandle.USER_SYSTEM) {
-                Log.i(TAG, "Current user is not USER_SYSTEM. Multi-user is not supported")
-                false
-            } else {
-                secureSettings.getIntForUser(SETTING_NAME, UNSET, UserHandle.USER_SYSTEM) != UNSET
-            }
+            secureSettings.getIntForUser(
+                SETTING_NAME,
+                UNSET,
+                userRepository.getSelectedUserInfo().id
+            ) != UNSET
         }
 
     /**
@@ -83,18 +87,17 @@
      *
      * @param value The new setting value to be applied.
      */
-    suspend fun setValue(value: Int) {
+    suspend fun setAutoOn(value: Int) {
         withContext(backgroundDispatcher) {
-            if (userRepository.getSelectedUserInfo().id != UserHandle.USER_SYSTEM) {
-                Log.i(TAG, "Current user is not USER_SYSTEM. Multi-user is not supported")
-            } else {
-                secureSettings.putIntForUser(SETTING_NAME, value, UserHandle.USER_SYSTEM)
-            }
+            secureSettings.putIntForUser(
+                SETTING_NAME,
+                value,
+                userRepository.getSelectedUserInfo().id
+            )
         }
     }
 
     companion object {
-        private const val TAG = "BluetoothAutoOnRepository"
         const val SETTING_NAME = "bluetooth_automatic_turn_on"
         const val UNSET = -1
     }
diff --git a/packages/SystemUI/src/com/android/systemui/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/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/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
index 1808d98..467089d 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
@@ -19,9 +19,11 @@
 package com.android.systemui.scene.shared.flag
 
 import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
+import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
 import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
 import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
 import com.android.systemui.Flags.keyguardBottomAreaRefactor
+import com.android.systemui.Flags.keyguardWmStateRefactor
 import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.Flags.sceneContainer
 import com.android.systemui.dagger.SysUISingleton
@@ -46,7 +48,8 @@
                 keyguardBottomAreaRefactor() &&
                 migrateClocksToBlueprint() &&
                 ComposeLockscreen.isEnabled &&
-                MediaInSceneContainerFlag.isEnabled
+                MediaInSceneContainerFlag.isEnabled &&
+                keyguardWmStateRefactor()
     // NOTE: Changes should also be made in getSecondaryFlags and @EnableSceneContainer
 
     /**
@@ -65,6 +68,7 @@
         sequenceOf(
             FlagToken(FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor()),
             FlagToken(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, migrateClocksToBlueprint()),
+            FlagToken(FLAG_KEYGUARD_WM_STATE_REFACTOR, keyguardWmStateRefactor()),
             ComposeLockscreen.token,
             MediaInSceneContainerFlag.token,
             // NOTE: Changes should also be made in isEnabled and @EnableSceneContainer
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index ac94f39..b2c01e1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -82,7 +82,7 @@
     private final RecordingController mController;
     protected final KeyguardDismissUtil mKeyguardDismissUtil;
     private final Handler mMainHandler;
-    private ScreenRecordingAudioSource mAudioSource;
+    private ScreenRecordingAudioSource mAudioSource = ScreenRecordingAudioSource.NONE;
     private boolean mShowTaps;
     private boolean mOriginalShowTaps;
     private ScreenMediaRecorder mRecorder;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
deleted file mode 100644
index 7e234ae..0000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2020 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.screenshot;
-
-import static com.android.systemui.screenshot.ScreenshotController.ACTION_TYPE_EDIT;
-import static com.android.systemui.screenshot.ScreenshotController.ACTION_TYPE_SHARE;
-import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_INTENT;
-import static com.android.systemui.screenshot.ScreenshotController.EXTRA_DISALLOW_ENTER_PIP;
-import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ID;
-import static com.android.systemui.screenshot.ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED;
-import static com.android.systemui.statusbar.phone.CentralSurfaces.SYSTEM_DIALOG_REASON_SCREENSHOT;
-
-import android.app.ActivityOptions;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.util.Log;
-import android.view.RemoteAnimationAdapter;
-import android.view.WindowManagerGlobal;
-
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.settings.DisplayTracker;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-
-import javax.inject.Inject;
-
-/**
- * Receiver to proxy the share or edit intent, used to clean up the notification and send
- * appropriate signals to the system (ie. to dismiss the keyguard if necessary).
- */
-public class ActionProxyReceiver extends BroadcastReceiver {
-    private static final String TAG = "ActionProxyReceiver";
-
-    private final ActivityManagerWrapper mActivityManagerWrapper;
-    private final ScreenshotSmartActions mScreenshotSmartActions;
-    private final DisplayTracker mDisplayTracker;
-    private final ActivityStarter mActivityStarter;
-
-    @Inject
-    public ActionProxyReceiver(ActivityManagerWrapper activityManagerWrapper,
-            ScreenshotSmartActions screenshotSmartActions,
-            DisplayTracker displayTracker,
-            ActivityStarter activityStarter) {
-        mActivityManagerWrapper = activityManagerWrapper;
-        mScreenshotSmartActions = screenshotSmartActions;
-        mDisplayTracker = displayTracker;
-        mActivityStarter = activityStarter;
-    }
-
-    @Override
-    public void onReceive(Context context, final Intent intent) {
-        Runnable startActivityRunnable = () -> {
-            mActivityManagerWrapper.closeSystemWindows(SYSTEM_DIALOG_REASON_SCREENSHOT);
-
-            PendingIntent actionIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT);
-            ActivityOptions opts = ActivityOptions.makeBasic();
-            opts.setDisallowEnterPictureInPictureWhileLaunching(
-                    intent.getBooleanExtra(EXTRA_DISALLOW_ENTER_PIP, false));
-            opts.setPendingIntentBackgroundActivityStartMode(
-                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
-            try {
-                actionIntent.send(context, 0, null, null, null, null, opts.toBundle());
-                if (intent.getBooleanExtra(ScreenshotController.EXTRA_OVERRIDE_TRANSITION, false)) {
-                    RemoteAnimationAdapter runner = new RemoteAnimationAdapter(
-                            ScreenshotController.SCREENSHOT_REMOTE_RUNNER, 0, 0);
-                    try {
-                        WindowManagerGlobal.getWindowManagerService()
-                                .overridePendingAppTransitionRemote(runner,
-                                        mDisplayTracker.getDefaultDisplayId());
-                    } catch (Exception e) {
-                        Log.e(TAG, "Error overriding screenshot app transition", e);
-                    }
-                }
-            } catch (PendingIntent.CanceledException e) {
-                Log.e(TAG, "Pending intent canceled", e);
-            }
-
-        };
-
-        mActivityStarter.executeRunnableDismissingKeyguard(startActivityRunnable, null,
-                true /* dismissShade */, true /* afterKeyguardGone */,
-                true /* deferred */);
-
-        if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) {
-            String actionType = Intent.ACTION_EDIT.equals(intent.getAction())
-                    ? ACTION_TYPE_EDIT
-                    : ACTION_TYPE_SHARE;
-            mScreenshotSmartActions.notifyScreenshotAction(
-                    intent.getStringExtra(EXTRA_ID), actionType, false, null);
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java
deleted file mode 100644
index e0346f2..0000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2020 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.screenshot;
-
-import static com.android.systemui.screenshot.ScreenshotController.ACTION_TYPE_DELETE;
-import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ID;
-import static com.android.systemui.screenshot.ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED;
-import static com.android.systemui.screenshot.ScreenshotController.SCREENSHOT_URI_ID;
-
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-
-import com.android.systemui.dagger.qualifiers.Background;
-
-import java.util.concurrent.Executor;
-
-import javax.inject.Inject;
-
-/**
- * Removes the file at a provided URI.
- */
-public class DeleteScreenshotReceiver extends BroadcastReceiver {
-
-    private final ScreenshotSmartActions mScreenshotSmartActions;
-    private final Executor mBackgroundExecutor;
-
-    @Inject
-    public DeleteScreenshotReceiver(ScreenshotSmartActions screenshotSmartActions,
-            @Background Executor backgroundExecutor) {
-        mScreenshotSmartActions = screenshotSmartActions;
-        mBackgroundExecutor = backgroundExecutor;
-    }
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        if (!intent.hasExtra(SCREENSHOT_URI_ID)) {
-            return;
-        }
-
-        // And delete the image from the media store
-        final Uri uri = Uri.parse(intent.getStringExtra(SCREENSHOT_URI_ID));
-        mBackgroundExecutor.execute(() -> {
-            ContentResolver resolver = context.getContentResolver();
-            resolver.delete(uri, null, null);
-        });
-        if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) {
-            mScreenshotSmartActions.notifyScreenshotAction(
-                    intent.getStringExtra(EXTRA_ID), ACTION_TYPE_DELETE, false, null);
-        }
-    }
-}
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/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index a343ded..db06c6b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -31,15 +31,6 @@
  * @see NotificationPanelViewController
  */
 interface ShadeViewController {
-    /**
-     * Returns whether the shade height is greater than zero or the shade is expecting a synthesized
-     * down event.
-     */
-    val isPanelExpanded: Boolean
-
-    /** Returns whether shade's height is zero. */
-    val isFullyCollapsed: Boolean
-
     /** Returns whether the shade is tracking touches for expand/collapse of the shade or QS. */
     val isTracking: Boolean
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
index c9140b5..48a2d75 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -39,6 +39,7 @@
     override fun animateCollapseQs(fullyCollapse: Boolean) {}
     override fun canBeCollapsed(): Boolean = false
     @Deprecated("Use ShadeAnimationInteractor instead") override val isCollapsing: Boolean = false
+    @Deprecated("Use !ShadeInteractor.isAnyExpanded instead")
     override val isFullyCollapsed: Boolean = false
     override val isTracking: Boolean = false
     override val isViewEnabled: Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt
index bd96a33..60cb061 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
@@ -43,6 +43,12 @@
     @Deprecated("Use SceneInteractor.currentScene instead.") val legacyPanelExpansion: Flow<Float>
 
     /**
+     * Returns whether the shade height is greater than zero or the shade is expecting a synthesized
+     * down event.
+     */
+    @Deprecated("Use ShadeInteractor.isAnyExpanded instead.") val isPanelExpanded: Boolean
+
+    /**
      * 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.
@@ -55,6 +61,9 @@
     )
     val isFullyExpanded: Boolean
 
+    /** Returns whether shade's height is zero. */
+    @Deprecated("Use !ShadeInteractor.isAnyExpanded instead") val isFullyCollapsed: Boolean
+
     /** Returns whether the shade is in the process of collapsing. */
     @Deprecated("Use ShadeAnimationInteractor instead") val isCollapsing: Boolean
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
index c5a6968..3877677 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
@@ -101,6 +101,9 @@
     )
     override val isFullyExpanded = shadeInteractor.isAnyFullyExpanded.value
 
+    @Deprecated("Use !ShadeInteractor.isAnyExpanded instead")
+    override val isFullyCollapsed = !shadeInteractor.isAnyExpanded.value
+
     @Deprecated("Use ShadeAnimationInteractor instead")
     override val isCollapsing =
         shadeAnimationInteractor.isAnyCloseAnimationRunning.value ||
@@ -108,4 +111,6 @@
     private fun SceneKey.isExpandable(): Boolean {
         return this == Scenes.Shade || this == Scenes.QuickSettings
     }
+
+    override val isPanelExpanded = shadeInteractor.isAnyExpanded.value
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
index 6414af3..421a761 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
@@ -46,7 +46,10 @@
     sharedNotificationContainerInteractor: SharedNotificationContainerInteractor,
     repository: ShadeRepository,
 ) : BaseShadeInteractor {
-    /** The amount [0-1] that the shade has been opened */
+    /**
+     * The amount [0-1] that the shade has been opened. Uses stateIn to avoid redundant calculations
+     * in downstream flows.
+     */
     override val shadeExpansion: Flow<Float> =
         combine(
                 repository.lockscreenShadeExpansion,
@@ -71,6 +74,7 @@
                 }
             }
             .distinctUntilChanged()
+            .stateIn(scope, SharingStarted.Eagerly, 0f)
 
     override val qsExpansion: StateFlow<Float> = repository.qsExpansion
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/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/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt
index 1bebbfd..ca73081 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt
@@ -18,12 +18,16 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.KeyguardOcclusionInteractor
+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.power.domain.interactor.PowerInteractor
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.distinctUntilChangedBy
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
@@ -46,6 +50,8 @@
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
     keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
     powerInteractor: PowerInteractor,
+    wmLockscreenVisibilityInteractor: WindowManagerLockscreenVisibilityInteractor,
+    surfaceBehindInteractor: KeyguardSurfaceBehindInteractor,
 ) {
     /** Occlusion state to apply whenever a keyguard transition is STARTED, if any. */
     private val occlusionStateFromStartedStep: Flow<OccludedState> =
@@ -98,11 +104,21 @@
                 OccludedState(occluded = occluded, animate = false)
             }
 
-    /** Occlusion state to apply to SKBVM's setOccluded call. */
+    /** Occlusion state to apply to SBKVM's setOccluded call. */
     val keyguardViewOcclusionState =
         merge(occlusionStateFromStartedStep, occlusionStateFromFinishedStep)
             .distinctUntilChangedBy {
                 // Don't switch 'animate' values mid-transition.
                 it.occluded
             }
+
+    /** Visibility state to apply to SBKVM via show() and hide(). */
+    val keyguardViewVisibility =
+        combine(
+                wmLockscreenVisibilityInteractor.lockscreenVisibility,
+                surfaceBehindInteractor.isAnimatingSurface,
+            ) { lockscreenVisible, animatingSurface ->
+                lockscreenVisible || animatingSurface
+            }
+            .distinctUntilChanged()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt
index 2707ed8..b77748e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.FSI_KEYGUARD_OCCLUDED
 import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.FSI_KEYGUARD_SHOWING
 import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.FSI_LOCKED_SHADE
+import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.FSI_USER_SETUP_INCOMPLETE
 import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_EXPECTED_TO_HUN
 import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_NOT_IMPORTANT_ENOUGH
 import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_NO_FULL_SCREEN_INTENT
@@ -101,6 +102,7 @@
         FSI_KEYGUARD_OCCLUDED(true, "keyguard is occluded"),
         FSI_LOCKED_SHADE(true, "locked shade"),
         FSI_DEVICE_NOT_PROVISIONED(true, "device not provisioned"),
+        FSI_USER_SETUP_INCOMPLETE(true, "user setup incomplete"),
         NO_FSI_NO_HUN_OR_KEYGUARD(
             false,
             "no HUN or keyguard",
@@ -189,6 +191,10 @@
             return FSI_DEVICE_NOT_PROVISIONED
         }
 
+        if (!deviceProvisionedController.isCurrentUserSetup) {
+            return FSI_USER_SETUP_INCOMPLETE
+        }
+
         return NO_FSI_NO_HUN_OR_KEYGUARD
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
index b0155f1..c084482 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
@@ -104,7 +104,11 @@
         /**
          * The device is not provisioned, launch FSI.
          */
-        FSI_NOT_PROVISIONED(true);
+        FSI_NOT_PROVISIONED(true),
+        /**
+         * The current user has not completed setup, launch FSI.
+         */
+        FSI_USER_SETUP_INCOMPLETE(true);
 
         public final boolean shouldLaunch;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index dc9eeb3..a655c72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -362,6 +362,12 @@
                     suppressedByDND);
         }
 
+        // The current user hasn't completed setup, launch FSI.
+        if (!mDeviceProvisionedController.isCurrentUserSetup()) {
+            return getDecisionGivenSuppression(FullScreenIntentDecision.FSI_USER_SETUP_INCOMPLETE,
+                    suppressedByDND);
+        }
+
         // Detect the case determined by b/231322873 to launch FSI while device is in use,
         // as blocked by the correct implementation, and report the event.
         return getDecisionGivenSuppression(FullScreenIntentDecision.NO_FSI_NO_HUN_OR_KEYGUARD,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 77e9425..9479762 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -49,6 +49,7 @@
 import android.graphics.Path;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.os.SystemClock;
 import android.os.Trace;
 import android.provider.Settings;
 import android.util.AttributeSet;
@@ -208,6 +209,9 @@
     private float mQsExpansionFraction;
     private final int mSplitShadeMinContentHeight;
     private String mLastUpdateSidePaddingDumpString;
+    private long mLastUpdateSidePaddingElapsedRealtime;
+    private String mLastInitViewDumpString;
+    private long mLastInitViewElapsedRealtime;
 
     /**
      * The algorithm which calculates the properties for our children
@@ -887,17 +891,34 @@
         mOverflingDistance = configuration.getScaledOverflingDistance();
 
         Resources res = context.getResources();
+        final boolean isSmallScreenLandscape = res.getBoolean(R.bool.is_small_screen_landscape);
         boolean useSmallLandscapeLockscreenResources = mIsSmallLandscapeLockscreenEnabled
-                && res.getBoolean(R.bool.is_small_screen_landscape);
+                && isSmallScreenLandscape;
         // TODO (b/293252410) remove condition here when flag is launched
         //  Instead update the config_skinnyNotifsInLandscape to be false whenever
         //  is_small_screen_landscape is true. Then, only use the config_skinnyNotifsInLandscape.
+        final boolean configSkinnyNotifsInLandscape = res.getBoolean(
+                R.bool.config_skinnyNotifsInLandscape);
         if (useSmallLandscapeLockscreenResources) {
             mSkinnyNotifsInLandscape = false;
         } else {
-            mSkinnyNotifsInLandscape = res.getBoolean(
-                    R.bool.config_skinnyNotifsInLandscape);
+            mSkinnyNotifsInLandscape = configSkinnyNotifsInLandscape;
         }
+
+        mLastInitViewDumpString =
+                "mIsSmallLandscapeLockscreenEnabled=" + mIsSmallLandscapeLockscreenEnabled
+                        + " isSmallScreenLandscape=" + isSmallScreenLandscape
+                        + " useSmallLandscapeLockscreenResources="
+                        + useSmallLandscapeLockscreenResources
+                        + " skinnyNotifsInLandscape=" + configSkinnyNotifsInLandscape
+                        + " mSkinnyNotifsInLandscape=" + mSkinnyNotifsInLandscape;
+        mLastInitViewElapsedRealtime = SystemClock.elapsedRealtime();
+
+        if (DEBUG_UPDATE_SIDE_PADDING) {
+            Log.v(TAG, "initView @ elapsedRealtime " + mLastInitViewElapsedRealtime + ": "
+                    + mLastInitViewDumpString);
+        }
+
         mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height);
         mStackScrollAlgorithm.initView(context);
         mStateAnimator.initView(context);
@@ -925,22 +946,33 @@
         mLastUpdateSidePaddingDumpString = "viewWidth=" + viewWidth
                 + " skinnyNotifsInLandscape=" + mSkinnyNotifsInLandscape
                 + " orientation=" + orientation;
+        mLastUpdateSidePaddingElapsedRealtime = SystemClock.elapsedRealtime();
 
         if (DEBUG_UPDATE_SIDE_PADDING) {
-            Log.v(TAG, "updateSidePadding: " + mLastUpdateSidePaddingDumpString);
+            Log.v(TAG,
+                    "updateSidePadding @ elapsedRealtime " + mLastUpdateSidePaddingElapsedRealtime
+                            + ": " + mLastUpdateSidePaddingDumpString);
         }
 
-        if (viewWidth == 0 || !mSkinnyNotifsInLandscape) {
+        if (viewWidth == 0) {
+            Log.e(TAG, "updateSidePadding: viewWidth is zero");
             mSidePaddings = mMinimumPaddings;
             return;
         }
 
-        // Portrait is easy, just use the dimen for paddings
         if (orientation == Configuration.ORIENTATION_PORTRAIT) {
             mSidePaddings = mMinimumPaddings;
             return;
         }
 
+        if (mShouldUseSplitNotificationShade) {
+            if (mSkinnyNotifsInLandscape) {
+                Log.e(TAG, "updateSidePadding: mSkinnyNotifsInLandscape has betrayed us!");
+            }
+            mSidePaddings = mMinimumPaddings;
+            return;
+        }
+
         final int innerWidth = viewWidth - mMinimumPaddings * 2;
         final int qsTileWidth = (innerWidth - mQsTilePadding * 3) / 4;
         mSidePaddings = mMinimumPaddings + qsTileWidth + mQsTilePadding;
@@ -4884,6 +4916,7 @@
 
     public void dump(PrintWriter pwOriginal, String[] args) {
         IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal);
+        final long elapsedRealtime = SystemClock.elapsedRealtime();
         pw.println("Internal state:");
         DumpUtilsKt.withIncreasedIndent(pw, () -> {
             println(pw, "pulsing", mPulsing);
@@ -4914,7 +4947,17 @@
             println(pw, "minimumPaddings", mMinimumPaddings);
             println(pw, "qsTilePadding", mQsTilePadding);
             println(pw, "sidePaddings", mSidePaddings);
+            println(pw, "elapsedRealtime", elapsedRealtime);
+            println(pw, "lastInitView", mLastInitViewDumpString);
+            println(pw, "lastInitViewElapsedRealtime", mLastInitViewElapsedRealtime);
+            println(pw, "lastInitViewMillisAgo", elapsedRealtime - mLastInitViewElapsedRealtime);
+            println(pw, "shouldUseSplitNotificationShade", mShouldUseSplitNotificationShade);
             println(pw, "lastUpdateSidePadding", mLastUpdateSidePaddingDumpString);
+            println(pw, "lastUpdateSidePaddingElapsedRealtime",
+                    mLastUpdateSidePaddingElapsedRealtime);
+            println(pw, "lastUpdateSidePaddingMillisAgo",
+                    elapsedRealtime - mLastUpdateSidePaddingElapsedRealtime);
+            println(pw, "isSmallLandscapeLockscreenEnabled", mIsSmallLandscapeLockscreenEnabled);
             mNotificationStackSizeCalculator.dump(pw, args);
         });
         pw.println();
@@ -5482,6 +5525,7 @@
             mAmbientState.setUseSplitShade(split);
             updateDismissBehavior();
             updateUseRoundedRectClipping();
+            requestLayout();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index ece7a7f..5b8b91c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewbinder
 
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
 import android.view.View
 import android.view.WindowInsets
 import androidx.lifecycle.Lifecycle
@@ -118,13 +116,6 @@
                                             "SharedNotificationContainerVB (collapseFadeIn)"
                                         )
                                     }
-                                    addListener(
-                                        object : AnimatorListenerAdapter() {
-                                            override fun onAnimationEnd(animation: Animator) {
-                                                viewModel.setShadeCollapseFadeInComplete(true)
-                                            }
-                                        }
-                                    )
                                     start()
                                 }
                             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 5e87a7a..2c4813a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -67,7 +67,6 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.currentCoroutineContext
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
@@ -79,6 +78,7 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.flow.transformWhile
 import kotlinx.coroutines.isActive
@@ -124,6 +124,23 @@
         setOf(AOD, LOCKSCREEN, DOZING, ALTERNATE_BOUNCER, PRIMARY_BOUNCER)
 
     /**
+     * Is either shade/qs expanded? This intentionally does not use the [ShadeInteractor] version,
+     * as the legacy implementation has extra logic that produces incorrect results.
+     */
+    private val isAnyExpanded =
+        combine(
+                shadeInteractor.shadeExpansion.map { it > 0f },
+                shadeInteractor.qsExpansion.map { it > 0f },
+            ) { shadeExpansion, qsExpansion ->
+                shadeExpansion || qsExpansion
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = false,
+            )
+
+    /**
      * Shade locked is a legacy concept, but necessary to mimic current functionality. Listen for
      * both SHADE_LOCKED and shade/qs expansion in order to determine lock state, as one can arrive
      * before the other.
@@ -131,17 +148,17 @@
     private val isShadeLocked: Flow<Boolean> =
         combine(
                 keyguardInteractor.statusBarState.map { it == SHADE_LOCKED },
-                shadeInteractor.qsExpansion.map { it > 0f },
-                shadeInteractor.shadeExpansion.map { it > 0f },
-            ) { isShadeLocked, isQsExpanded, isShadeExpanded ->
-                isShadeLocked && (isQsExpanded || isShadeExpanded)
+                isAnyExpanded,
+            ) { isShadeLocked, isAnyExpanded ->
+                isShadeLocked && isAnyExpanded
             }
-            .distinctUntilChanged()
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = false,
+            )
             .dumpWhileCollecting("isShadeLocked")
 
-    private val shadeCollapseFadeInComplete =
-        MutableStateFlow(false).dumpValue("shadeCollapseFadeInComplete")
-
     val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
         interactor.configurationBasedDimensions
             .map {
@@ -176,20 +193,16 @@
             ) { constrainedNotificationState, transitioningToOrFromLockscreen ->
                 constrainedNotificationState || transitioningToOrFromLockscreen
             }
-            .distinctUntilChanged()
+            .shareIn(scope = applicationScope, started = SharingStarted.Eagerly)
             .dumpWhileCollecting("isOnLockscreen")
 
     /** Are we purely on the keyguard without the shade/qs? */
     val isOnLockscreenWithoutShade: Flow<Boolean> =
         combine(
                 isOnLockscreen,
-                // Shade with notifications
-                shadeInteractor.shadeExpansion.map { it > 0f },
-                // Shade without notifications, quick settings only (pull down from very top on
-                // lockscreen)
-                shadeInteractor.qsExpansion.map { it > 0f },
-            ) { isKeyguard, isShadeVisible, qsExpansion ->
-                isKeyguard && !(isShadeVisible || qsExpansion)
+                isAnyExpanded,
+            ) { isKeyguard, isAnyExpanded ->
+                isKeyguard && !isAnyExpanded
             }
             .stateIn(
                 scope = applicationScope,
@@ -219,13 +232,9 @@
     val isOnGlanceableHubWithoutShade: Flow<Boolean> =
         combine(
                 isOnGlanceableHub,
-                // Shade with notifications
-                shadeInteractor.shadeExpansion.map { it > 0f },
-                // Shade without notifications, quick settings only (pull down from very top on
-                // lockscreen)
-                shadeInteractor.qsExpansion.map { it > 0f },
-            ) { isGlanceableHub, isShadeVisible, qsExpansion ->
-                isGlanceableHub && !(isShadeVisible || qsExpansion)
+                isAnyExpanded,
+            ) { isGlanceableHub, isAnyExpanded ->
+                isGlanceableHub && !isAnyExpanded
             }
             .stateIn(
                 scope = applicationScope,
@@ -283,9 +292,6 @@
                     awaitCollapse().collect { doFadeIn ->
                         if (doFadeIn) {
                             emit(true)
-                            // ... and then for the animation to complete
-                            shadeCollapseFadeInComplete.first { it }
-                            shadeCollapseFadeInComplete.value = false
                         }
                     }
                 }
@@ -295,7 +301,7 @@
                 started = SharingStarted.WhileSubscribed(),
                 initialValue = false,
             )
-            .dumpWhileCollecting("shadeCollapseFadeIn")
+            .dumpValue("shadeCollapseFadeIn")
 
     /**
      * The container occupies the entire screen, and must be positioned relative to other elements.
@@ -323,7 +329,7 @@
                     // When QS expansion > 0, it should directly set the top padding so do not
                     // animate it
                     val animate = qsExpansion == 0f && !isInTransitionToAnyState
-                    keyguardInteractor.notificationContainerBounds.value.copy(
+                    bounds.copy(
                         top = top,
                         isAnimated = animate,
                     )
@@ -547,10 +553,6 @@
         interactor.notificationStackChanged()
     }
 
-    fun setShadeCollapseFadeInComplete(complete: Boolean) {
-        shadeCollapseFadeInComplete.value = complete
-    }
-
     data class ConfigurationBasedDimensions(
         val marginStart: Int,
         val marginTop: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index ab9ecab..1906b7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -57,8 +57,8 @@
 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;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -81,6 +81,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;
@@ -95,7 +96,6 @@
     private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
     private final PowerManager mPowerManager;
     private final Optional<Vibrator> mVibratorOptional;
-    private final DisableFlagsLogger mDisableFlagsLogger;
     private final int mDisplayId;
     private final UserTracker mUserTracker;
     private final boolean mVibrateOnOpening;
@@ -120,6 +120,7 @@
             ShadeController shadeController,
             CommandQueue commandQueue,
             ShadeViewController shadeViewController,
+            PanelExpansionInteractor panelExpansionInteractor,
             RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
             MetricsLogger metricsLogger,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -134,7 +135,6 @@
             StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager,
             PowerManager powerManager,
             Optional<Vibrator> vibratorOptional,
-            DisableFlagsLogger disableFlagsLogger,
             @DisplayId int displayId,
             Lazy<CameraLauncher> cameraLauncherLazy,
             UserTracker userTracker,
@@ -147,6 +147,7 @@
         mShadeController = shadeController;
         mCommandQueue = commandQueue;
         mShadeViewController = shadeViewController;
+        mPanelExpansionInteractor = panelExpansionInteractor;
         mRemoteInputQuickSettingsDisabler = remoteInputQuickSettingsDisabler;
         mMetricsLogger = metricsLogger;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -161,7 +162,6 @@
         mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager;
         mPowerManager = powerManager;
         mVibratorOptional = vibratorOptional;
-        mDisableFlagsLogger = disableFlagsLogger;
         mDisplayId = displayId;
         mCameraLauncherLazy = cameraLauncherLazy;
         mUserTracker = userTracker;
@@ -304,7 +304,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 +371,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
@@ -485,7 +485,7 @@
 
     @Override
     public void togglePanel() {
-        if (mShadeViewController.isPanelExpanded()) {
+        if (mPanelExpansionInteractor.isPanelExpanded()) {
             mShadeController.animateCollapseShade();
         } else {
             mShadeController.animateExpandShade();
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/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..f99817a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -23,7 +23,6 @@
 import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
-import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows;
 
 import android.content.Context;
 import android.content.res.ColorStateList;
@@ -76,6 +75,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;
@@ -114,8 +115,10 @@
 
 import javax.inject.Inject;
 
+import kotlin.Unit;
 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 +166,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,20 +497,31 @@
             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.
             mJavaAdapter.alwaysCollectFlow(
-                    combineFlows(
-                            mWmLockscreenVisibilityInteractor.get().getLockscreenVisibility(),
-                            mSurfaceBehindInteractor.get().isAnimatingSurface(),
-                            (lockscreenVis, animatingSurface) ->
-                                    // TODO(b/322546110): Waiting until we're not animating the
-                                    // surface is a workaround to avoid jank. We should actually
-                                    // fix the source of the jank, and then hide the keyguard
-                                    // view without waiting for the animation to end.
-                                    lockscreenVis || animatingSurface
-                    ),
+                    mStatusBarKeyguardViewManagerInteractor.getKeyguardViewVisibility(),
                     this::consumeShowStatusBarKeyguardView);
 
             mJavaAdapter.alwaysCollectFlow(
@@ -514,6 +531,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 +1491,6 @@
         mPrimaryBouncerInteractor.notifyKeyguardAuthenticatedBiometrics(strongAuth);
 
         if (mAlternateBouncerInteractor.isVisibleState()) {
-            hideAlternateBouncer(false);
             executeAfterKeyguardGoneAction();
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index b5ab4e3..5d27467 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -61,7 +61,7 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationClickNotifier;
@@ -115,7 +115,7 @@
     private final Lazy<AssistManager> mAssistManagerLazy;
     private final NotificationRemoteInputManager mRemoteInputManager;
     private final NotificationLockscreenUserManager mLockscreenUserManager;
-    private final com.android.systemui.shade.ShadeController mShadeController;
+    private final ShadeController mShadeController;
     private final KeyguardStateController mKeyguardStateController;
     private final LockPatternUtils mLockPatternUtils;
     private final StatusBarRemoteInputCallback mStatusBarRemoteInputCallback;
@@ -126,7 +126,7 @@
     private final StatusBarNotificationActivityStarterLogger mLogger;
 
     private final NotificationPresenter mPresenter;
-    private final ShadeViewController mShadeViewController;
+    private final PanelExpansionInteractor mPanelExpansionInteractor;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final ActivityTransitionAnimator mActivityTransitionAnimator;
     private final NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider;
@@ -163,7 +163,7 @@
             StatusBarNotificationActivityStarterLogger logger,
             OnUserInteractionCallback onUserInteractionCallback,
             NotificationPresenter presenter,
-            ShadeViewController shadeViewController,
+            PanelExpansionInteractor panelExpansionInteractor,
             NotificationShadeWindowController notificationShadeWindowController,
             ActivityTransitionAnimator activityTransitionAnimator,
             ShadeAnimationInteractor shadeAnimationInteractor,
@@ -192,13 +192,13 @@
         mLockPatternUtils = lockPatternUtils;
         mStatusBarRemoteInputCallback = remoteInputCallback;
         mActivityIntentHelper = activityIntentHelper;
+        mPanelExpansionInteractor = panelExpansionInteractor;
         mNotificationShadeWindowController = notificationShadeWindowController;
         mShadeAnimationInteractor = shadeAnimationInteractor;
         mMetricsLogger = metricsLogger;
         mLogger = logger;
         mOnUserInteractionCallback = onUserInteractionCallback;
         mPresenter = presenter;
-        mShadeViewController = shadeViewController;
         mActivityTransitionAnimator = activityTransitionAnimator;
         mNotificationAnimationProvider = notificationAnimationProvider;
         mPowerInteractor = powerInteractor;
@@ -296,7 +296,7 @@
         }
 
         // Always defer the keyguard dismiss when animating.
-        return animate || !mShadeViewController.isFullyCollapsed();
+        return animate || !mPanelExpansionInteractor.isFullyCollapsed();
     }
 
     private void handleNotificationClickAfterPanelCollapsed(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 5b14259..5a3ae73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -236,7 +236,7 @@
 
     @Override
     public boolean isPresenterFullyCollapsed() {
-        return mNotificationPanel.isFullyCollapsed();
+        return mPanelExpansionInteractor.isFullyCollapsed();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 88374d6..67d2299 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -12,16 +12,19 @@
 import android.view.Surface
 import android.view.View
 import android.view.WindowManager.fixScale
+import com.android.app.animation.Interpolators
+import com.android.app.tracing.namedRunnable
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF
 import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
 import com.android.systemui.DejankUtils
-import com.android.app.animation.Interpolators
+import com.android.systemui.Flags.lightRevealMigration
 import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.KeyguardViewMediator
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
 import com.android.systemui.statusbar.CircleReveal
 import com.android.systemui.statusbar.LightRevealScrim
 import com.android.systemui.statusbar.NotificationShadeWindowController
@@ -32,9 +35,8 @@
 import com.android.systemui.statusbar.notification.stack.AnimationProperties
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
 import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.Flags.lightRevealMigration
-import com.android.app.tracing.namedRunnable
 import com.android.systemui.util.settings.GlobalSettings
+import dagger.Lazy
 import javax.inject.Inject
 
 /**
@@ -60,14 +62,15 @@
     private val context: Context,
     private val wakefulnessLifecycle: WakefulnessLifecycle,
     private val statusBarStateControllerImpl: StatusBarStateControllerImpl,
-    private val keyguardViewMediatorLazy: dagger.Lazy<KeyguardViewMediator>,
+    private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>,
     private val keyguardStateController: KeyguardStateController,
-    private val dozeParameters: dagger.Lazy<DozeParameters>,
+    private val dozeParameters: Lazy<DozeParameters>,
     private val globalSettings: GlobalSettings,
-    private val notifShadeWindowControllerLazy: dagger.Lazy<NotificationShadeWindowController>,
+    private val notifShadeWindowControllerLazy: Lazy<NotificationShadeWindowController>,
     private val interactionJankMonitor: InteractionJankMonitor,
     private val powerManager: PowerManager,
-    private val handler: Handler = Handler()
+    private val panelExpansionInteractorLazy: Lazy<PanelExpansionInteractor>,
+    private val handler: Handler = Handler(),
 ) : WakefulnessLifecycle.Observer, ScreenOffAnimation {
     private lateinit var centralSurfaces: CentralSurfaces
     private lateinit var shadeViewController: ShadeViewController
@@ -346,7 +349,7 @@
         // already expanded and showing notifications/QS, the animation looks really messy. For now,
         // disable it if the notification panel is expanded.
         if ((!this::centralSurfaces.isInitialized ||
-                shadeViewController.isPanelExpanded) &&
+                    panelExpansionInteractorLazy.get().isPanelExpanded) &&
                 // Status bar might be expanded because we have started
                 // playing the animation already
                 !isAnimationPlaying()
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
index 6aaf5d6..55a0f59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -131,21 +131,37 @@
     }
 
     /**
-     * Returns true if given HeadsUpEntry is the last one tracked by AvalancheController. Used by
-     * BaseHeadsUpManager.HeadsUpEntry.calculateFinishTime to shorten display duration during active
-     * avalanche.
+     * Returns duration based on
+     * 1) Whether HeadsUpEntry is the last one tracked byAvalancheController
+     * 2) The priority of the top HUN in the next batch Used by
+     *    BaseHeadsUpManager.HeadsUpEntry.calculateFinishTime to shorten display duration.
      */
-    fun shortenDuration(entry: HeadsUpEntry): Boolean {
+    fun getDurationMs(entry: HeadsUpEntry, autoDismissMs: Int): Int {
         if (!NotificationThrottleHun.isEnabled) {
-            // Use default display duration, like we always did before AvalancheController existed
-            return false
+            // Use default duration, like we did before AvalancheController existed
+            return autoDismissMs
         }
         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
+        val entryList = showingList + nextList
+        if (entryList.indexOf(entry) == entryList.size - 1) {
+            // Use default duration if last entry
+            return autoDismissMs
+        }
+
+        nextList.sort()
+        val nextEntry = nextList[0]
+
+        if (nextEntry.compareNonTimeFields(entry) == -1) {
+            // Next entry is higher priority
+            return 500
+        } else if (nextEntry.compareNonTimeFields(entry) == 0) {
+            // Next entry is same priority
+            return 1000
+        } else {
+            return autoDismissMs
+        }
     }
 
     /**
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 05cc73e..50de3cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -776,7 +776,7 @@
             return mEarliestRemovalTime < mSystemClock.elapsedRealtime();
         }
 
-        public int compareTo(@NonNull HeadsUpEntry headsUpEntry) {
+        public int compareNonTimeFields(HeadsUpEntry headsUpEntry) {
             boolean isPinned = mEntry.isRowPinned();
             boolean otherPinned = headsUpEntry.mEntry.isRowPinned();
             if (isPinned && !otherPinned) {
@@ -806,7 +806,14 @@
             } else if (!mRemoteInputActive && headsUpEntry.mRemoteInputActive) {
                 return 1;
             }
+            return 0;
+        }
 
+        public int compareTo(@NonNull HeadsUpEntry headsUpEntry) {
+            int nonTimeCompareResult = compareNonTimeFields(headsUpEntry);
+            if (nonTimeCompareResult != 0) {
+                return nonTimeCompareResult;
+            }
             if (mPostTime > headsUpEntry.mPostTime) {
                 return -1;
             } else if (mPostTime == headsUpEntry.mPostTime) {
@@ -929,10 +936,8 @@
             int requestedTimeOutMs;
             if (isStickyForSomeTime()) {
                 requestedTimeOutMs = mStickyForSomeTimeAutoDismissTime;
-            } else if (mAvalancheController.shortenDuration(this)) {
-                requestedTimeOutMs = 1000;
             } else {
-                requestedTimeOutMs = mAutoDismissTime;
+                requestedTimeOutMs = mAvalancheController.getDurationMs(this, 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/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index b5efc44..aac18e3 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -86,15 +86,6 @@
 import com.android.systemui.util.settings.SecureSettings;
 
 import com.google.ux.material.libmonet.dynamiccolor.MaterialDynamicColors;
-import com.google.ux.material.libmonet.hct.Hct;
-import com.google.ux.material.libmonet.scheme.DynamicScheme;
-import com.google.ux.material.libmonet.scheme.SchemeExpressive;
-import com.google.ux.material.libmonet.scheme.SchemeFruitSalad;
-import com.google.ux.material.libmonet.scheme.SchemeMonochrome;
-import com.google.ux.material.libmonet.scheme.SchemeNeutral;
-import com.google.ux.material.libmonet.scheme.SchemeRainbow;
-import com.google.ux.material.libmonet.scheme.SchemeTonalSpot;
-import com.google.ux.material.libmonet.scheme.SchemeVibrant;
 
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -150,7 +141,7 @@
     // Dominant color extracted from wallpaper, NOT the color used on the overlay
     protected int mMainWallpaperColor = Color.TRANSPARENT;
     // UI contrast as reported by UiModeManager
-    private float mContrast = 0;
+    private double mContrast = 0.0;
     // Theme variant: Vibrant, Tonal, Expressive, etc
     @VisibleForTesting
     protected Style mThemeStyle = Style.TONAL_SPOT;
@@ -170,8 +161,8 @@
     private final JavaAdapter mJavaAdapter;
     private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     private final UiModeManager mUiModeManager;
-    private DynamicScheme mDynamicSchemeDark;
-    private DynamicScheme mDynamicSchemeLight;
+    private ColorScheme mDarkColorScheme;
+    private ColorScheme mLightColorScheme;
 
     // Defers changing themes until Setup Wizard is done.
     private boolean mDeferredThemeEvaluation;
@@ -210,7 +201,7 @@
             boolean currentUser = userId == mUserTracker.getUserId();
             boolean isAsleep = themeOverlayControllerWakefulnessDeprecation()
                     ? mKeyguardTransitionInteractor.isFinishedInStateWhereValue(
-                        state -> KeyguardState.Companion.deviceIsAsleepInState(state))
+                    KeyguardState.Companion::deviceIsAsleepInState)
                     : mWakefulnessLifecycle.getWakefulness() != WAKEFULNESS_ASLEEP;
 
             if (currentUser && !mAcceptColorEvents && isAsleep) {
@@ -483,19 +474,14 @@
             reevaluateSystemTheme(true /* forceReload */);
         });
 
+        // All wallpaper color and keyguard logic only applies when Monet is enabled.
         if (!mIsMonetEnabled) {
             return;
         }
 
         mUserTracker.addCallback(mUserTrackerCallback, mMainExecutor);
-
         mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
 
-        // All wallpaper color and keyguard logic only applies when Monet is enabled.
-        if (!mIsMonetEnabled) {
-            return;
-        }
-
         // Upon boot, make sure we have the most up to date colors
         Runnable updateColors = () -> {
             WallpaperColors systemColor = mWallpaperManager.getWallpaperColors(
@@ -589,29 +575,6 @@
         return ColorScheme.getSeedColor(wallpaperColors);
     }
 
-    private static DynamicScheme dynamicSchemeFromStyle(Style style, int color,
-            boolean isDark, double contrastLevel) {
-        Hct sourceColorHct = Hct.fromInt(color);
-        switch (style) {
-            case EXPRESSIVE:
-                return new SchemeExpressive(sourceColorHct, isDark, contrastLevel);
-            case SPRITZ:
-                return new SchemeNeutral(sourceColorHct, isDark, contrastLevel);
-            case TONAL_SPOT:
-                return new SchemeTonalSpot(sourceColorHct, isDark, contrastLevel);
-            case FRUIT_SALAD:
-                return new SchemeFruitSalad(sourceColorHct, isDark, contrastLevel);
-            case RAINBOW:
-                return new SchemeRainbow(sourceColorHct, isDark, contrastLevel);
-            case VIBRANT:
-                return new SchemeVibrant(sourceColorHct, isDark, contrastLevel);
-            case MONOCHROMATIC:
-                return new SchemeMonochrome(sourceColorHct, isDark, contrastLevel);
-            default:
-                return null;
-        }
-    }
-
     @VisibleForTesting
     protected boolean isNightMode() {
         return (mResources.getConfiguration().uiMode
@@ -626,22 +589,17 @@
     @VisibleForTesting
     protected boolean isPrivateProfile(UserHandle userHandle) {
         Context usercontext = mContext.createContextAsUser(userHandle,0);
-        if (usercontext.getSystemService(UserManager.class).isPrivateProfile()) {
-            return true;
-        }
-        return false;
+        return usercontext.getSystemService(UserManager.class).isPrivateProfile();
     }
 
     private void createOverlays(int color) {
-        boolean nightMode = isNightMode();
-        mColorScheme = new ColorScheme(color, nightMode, mThemeStyle);
+        mDarkColorScheme = new ColorScheme(color, true /* isDark */, mThemeStyle, mContrast);
+        mLightColorScheme = new ColorScheme(color, false /* isDark */, mThemeStyle, mContrast);
+        mColorScheme = isNightMode() ? mDarkColorScheme : mLightColorScheme;
+
         mNeutralOverlay = createNeutralOverlay();
         mSecondaryOverlay = createAccentOverlay();
 
-        mDynamicSchemeDark = dynamicSchemeFromStyle(
-                mThemeStyle, color, true /* isDark */, mContrast);
-        mDynamicSchemeLight = dynamicSchemeFromStyle(
-                mThemeStyle, color, false /* isDark */, mContrast);
         mDynamicOverlay = createDynamicOverlay();
     }
 
@@ -682,10 +640,10 @@
 
     private void assignDynamicPaletteToOverlay(FabricatedOverlay overlay, boolean isDark) {
         String suffix = isDark ? "dark" : "light";
-        DynamicScheme scheme = isDark ? mDynamicSchemeDark : mDynamicSchemeLight;
+        ColorScheme scheme = isDark ? mDarkColorScheme : mLightColorScheme;
         DynamicColors.allDynamicColorsMapped(mIsFidelityEnabled).forEach(p -> {
             String resourceName = "android:color/system_" + p.first + "_" + suffix;
-            int colorValue = p.second.getArgb(scheme);
+            int colorValue = p.second.getArgb(scheme.getMaterialScheme());
             overlay.setResourceValue(resourceName, TYPE_INT_COLOR_ARGB8, colorValue,
                     null /* configuration */);
         });
@@ -694,7 +652,7 @@
     private void assignFixedColorsToOverlay(FabricatedOverlay overlay) {
         DynamicColors.getFixedColorsMapped(mIsFidelityEnabled).forEach(p -> {
             String resourceName = "android:color/system_" + p.first;
-            int colorValue = p.second.getArgb(mDynamicSchemeLight);
+            int colorValue = p.second.getArgb(mLightColorScheme.getMaterialScheme());
             overlay.setResourceValue(resourceName, TYPE_INT_COLOR_ARGB8, colorValue,
                     null /* configuration */);
         });
@@ -702,6 +660,7 @@
 
     /**
      * Checks if the color scheme in mColorScheme matches the current system palettes.
+     *
      * @param managedProfiles List of managed profiles for this user.
      */
     private boolean colorSchemeIsApplied(Set<UserHandle> managedProfiles) {
@@ -723,15 +682,18 @@
                     && res.getColor(android.R.color.system_neutral2_500, theme)
                     == mColorScheme.getNeutral2().getS500()
                     && res.getColor(android.R.color.system_outline_variant_dark, theme)
-                    == dynamicColors.outlineVariant().getArgb(mDynamicSchemeDark)
+                    == dynamicColors.outlineVariant().getArgb(mDarkColorScheme.getMaterialScheme())
                     && res.getColor(android.R.color.system_outline_variant_light, theme)
-                    == dynamicColors.outlineVariant().getArgb(mDynamicSchemeLight)
+                    == dynamicColors.outlineVariant().getArgb(mLightColorScheme.getMaterialScheme())
                     && res.getColor(android.R.color.system_primary_container_dark, theme)
-                    == dynamicColors.primaryContainer().getArgb(mDynamicSchemeDark)
+                    == dynamicColors.primaryContainer().getArgb(
+                    mDarkColorScheme.getMaterialScheme())
                     && res.getColor(android.R.color.system_primary_container_light, theme)
-                    == dynamicColors.primaryContainer().getArgb(mDynamicSchemeLight)
+                    == dynamicColors.primaryContainer().getArgb(
+                    mLightColorScheme.getMaterialScheme())
                     && res.getColor(android.R.color.system_primary_fixed, theme)
-                    == dynamicColors.primaryFixed().getArgb(mDynamicSchemeLight))) {
+                    == dynamicColors.primaryFixed().getArgb(
+                    mLightColorScheme.getMaterialScheme()))) {
                 return false;
             }
         }
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/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 7674fe9..7931fab 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -259,6 +259,11 @@
             public void moveFocusedTaskToFullscreen(int displayId) {
                 splitScreen.goToFullscreenFromSplit();
             }
+
+            @Override
+            public void setSplitscreenFocus(boolean leftOrTop) {
+                splitScreen.setSplitscreenFocus(leftOrTop);
+            }
         });
     }
 
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/AndroidTest.xml b/packages/SystemUI/tests/AndroidTest.xml
index 31e7bd2..cd2a62d 100644
--- a/packages/SystemUI/tests/AndroidTest.xml
+++ b/packages/SystemUI/tests/AndroidTest.xml
@@ -32,4 +32,11 @@
         <option name="test-filter-dir" value="/data/data/com.android.systemui.tests" />
         <option name="hidden-api-checks" value="false"/>
     </test>
+
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="directory-keys"
+            value="/data/user/0/com.android.systemui.tests/files"/>
+        <option name="collect-on-run-ended-only" value="true"/>
+        <option name="clean-up" value="true"/>
+    </metrics_collector>
 </configuration>
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/SystemActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java
index b478d5c..c674294 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java
@@ -17,6 +17,7 @@
 package com.android.systemui.accessibility;
 
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doAnswer;
@@ -38,7 +39,7 @@
 import com.android.systemui.settings.FakeDisplayTracker;
 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.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -65,7 +66,7 @@
     @Mock
     private ShadeController mShadeController;
     @Mock
-    private ShadeViewController mShadeViewController;
+    private PanelExpansionInteractor mPanelExpansionInteractor;
     @Mock
     private Optional<Recents> mRecentsOptional;
     @Mock
@@ -87,7 +88,7 @@
                 mNotificationShadeController,
                 mKeyguardStateController,
                 mShadeController,
-                () -> mShadeViewController,
+                () -> mPanelExpansionInteractor,
                 mRecentsOptional,
                 mDisplayTracker);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index 64936862..69cd592 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -476,12 +476,18 @@
             mValueAnimator.end();
         });
 
-        verify(mSpyController).enableWindowMagnificationInternal(
+        // Verify the method is called in
+        // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once and
+        // {@link Animator.AnimatorListener#onAnimationEnd} once in {@link ValueAnimator#end()}
+        verify(mSpyController, times(2)).enableWindowMagnificationInternal(
                 mScaleCaptor.capture(),
                 mCenterXCaptor.capture(), mCenterYCaptor.capture(),
                 mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
-        //Animating in reverse, so we only check if the start values are greater than current.
+
+        // The animation is playing forwards, so we only check the animated values are greater than
+        // the current one. (Asserting the first captured scale)
         assertTrue(mScaleCaptor.getAllValues().get(0) > mCurrentScale.get());
+        // The last captured scale equalsto the targetScale when we enable window magnification.
         assertEquals(targetScale, mScaleCaptor.getValue(), 0f);
         assertTrue(mCenterXCaptor.getAllValues().get(0) > mCurrentCenterX.get());
         assertEquals(targetCenterX, mCenterXCaptor.getValue(), 0f);
@@ -588,10 +594,10 @@
         final float expectedY = (int) (windowBounds.exactCenterY() + expectedOffset
                 - defaultMagnificationWindowSize / 2);
 
-        // This is called 4 times when (1) first creating WindowlessMirrorWindow (2) SurfaceView is
+        // This is called 5 times when (1) first creating WindowlessMirrorWindow (2) SurfaceView is
         // created and we place the mirrored content as a child of the SurfaceView
-        // (3) the animation starts (4) the animation updates
-        verify(mTransaction, times(4))
+        // (3) the animation starts (4) the animation updates (5) the animation ends
+        verify(mTransaction, times(5))
                 .setPosition(any(SurfaceControl.class), eq(expectedX), eq(expectedY));
     }
 
@@ -781,16 +787,18 @@
         // wait for animation returns
         waitForIdleSync();
 
-        // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)} will only
-        // be triggered once in {@link ValueAnimator#end()}
-        verify(mSpyController).enableWindowMagnificationInternal(
+        // Verify the method is called in
+        // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once and
+        // {@link Animator.AnimatorListener#onAnimationEnd} once in {@link ValueAnimator#end()}
+        verify(mSpyController, times(2)).enableWindowMagnificationInternal(
                 mScaleCaptor.capture(),
                 mCenterXCaptor.capture(), mCenterYCaptor.capture(),
                 mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
 
-        //The animation is in verse, so we only check the start values should no be greater than
-        // the current one.
+        // The animation is playing backwards, so we only check the animated values should not be
+        // greater than the current one. (Asserting the first captured scale)
         assertTrue(mScaleCaptor.getAllValues().get(0) <= mCurrentScale.get());
+        // The last captured scale is 1.0 when we delete window magnification.
         assertEquals(1.0f, mScaleCaptor.getValue(), 0f);
         verifyStartValue(mCenterXCaptor, Float.NaN);
         verifyStartValue(mCenterYCaptor, Float.NaN);
@@ -823,10 +831,15 @@
         resetMockObjects();
         deleteWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback2);
 
-        verify(mSpyController).enableWindowMagnificationInternal(
+        // Verify the method is called in
+        // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once and
+        // {@link Animator.AnimatorListener#onAnimationEnd} once when running the animation at
+        // the final duration time.
+        verify(mSpyController, times(2)).enableWindowMagnificationInternal(
                 mScaleCaptor.capture(),
                 mCenterXCaptor.capture(), mCenterYCaptor.capture(),
                 mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
+        // The last captured scale is 1.0 when we delete window magnification.
         assertEquals(1.0f, mScaleCaptor.getValue(), 0f);
         verifyStartValue(mOffsetXCaptor, 0f);
         verifyStartValue(mOffsetYCaptor, 0f);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
index 08b49e7..cbdc696 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
@@ -132,7 +132,8 @@
 public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiTestCase {
 
     @Rule
-    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this);
+    // NOTE: pass 'null' to allow this test advances time on the main thread.
+    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(/* test= */ null);
     @Rule
     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
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 f76957f..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
@@ -500,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 2b4e9ec..072569d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -39,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
@@ -375,6 +376,7 @@
 
     @Test
     fun testShowBiometricUI() {
+        mSetFlagsRule.disableFlags(FLAG_CONSTRAINT_BP)
         val container = initializeFingerprintContainer()
 
         waitForIdleSync()
@@ -397,6 +399,7 @@
     @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/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/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
index 7ee8963..1839d8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
@@ -37,8 +37,6 @@
 import com.android.systemui.testKosmos
 import com.android.systemui.user.domain.interactor.selectedUserInteractor
 import junit.framework.Assert.assertEquals
-import junit.framework.Assert.assertTrue
-import junit.framework.Assert.fail
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runCurrent
@@ -144,73 +142,6 @@
         }
 
     @Test
-    fun testSurfaceBehindModel() =
-        testScope.runTest {
-            val values by collectValues(underTest.surfaceBehindModel)
-
-            transitionRepository.sendTransitionStep(
-                TransitionStep(
-                    transitionState = TransitionState.STARTED,
-                    from = KeyguardState.PRIMARY_BOUNCER,
-                    to = KeyguardState.LOCKSCREEN,
-                )
-            )
-            runCurrent()
-
-            assertEquals(
-                listOf(
-                    null, // PRIMARY_BOUNCER -> LOCKSCREEN does not have specific view params.
-                ),
-                values
-            )
-
-            transitionRepository.sendTransitionStep(
-                TransitionStep(
-                    transitionState = TransitionState.STARTED,
-                    from = KeyguardState.PRIMARY_BOUNCER,
-                    to = KeyguardState.GONE,
-                )
-            )
-            runCurrent()
-
-            transitionRepository.sendTransitionStep(
-                TransitionStep(
-                    transitionState = TransitionState.RUNNING,
-                    from = KeyguardState.PRIMARY_BOUNCER,
-                    to = KeyguardState.GONE,
-                    value = 0.01f,
-                )
-            )
-            runCurrent()
-
-            transitionRepository.sendTransitionStep(
-                TransitionStep(
-                    transitionState = TransitionState.RUNNING,
-                    from = KeyguardState.PRIMARY_BOUNCER,
-                    to = KeyguardState.GONE,
-                    value = 0.99f,
-                )
-            )
-            runCurrent()
-
-            assertEquals(3, values.size)
-            val model1percent = values[1]
-            val model99percent = values[2]
-
-            try {
-                // We should initially have an alpha of 0f when unlocking, so the surface is not
-                // visible
-                // while lockscreen UI animates out.
-                assertEquals(0f, model1percent!!.alpha)
-
-                // By the end it should probably be visible.
-                assertTrue(model99percent!!.alpha > 0f)
-            } catch (e: NullPointerException) {
-                fail("surfaceBehindModel was unexpectedly null.")
-            }
-        }
-
-    @Test
     @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
     fun testReturnToLockscreen_whenBouncerHides() =
         testScope.runTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
index 170d348..b6b4571 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
@@ -17,6 +17,8 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags
@@ -76,6 +78,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
     fun testAppliesSplitShadeBlueprint() {
         testScope.runTest {
             val blueprint by collectLastValue(underTest.blueprint)
@@ -88,6 +91,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
     fun fingerprintPropertyInitialized_updatesBlueprint() {
         testScope.runTest {
             val blueprint by collectLastValue(underTest.blueprint)
@@ -99,9 +103,9 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
     fun composeLockscreenOff_DoesAppliesSplitShadeWeatherClockBlueprint() {
         testScope.runTest {
-            mSetFlagsRule.disableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
             val blueprint by collectLastValue(underTest.blueprint)
             whenever(clockController.config)
                 .thenReturn(
@@ -121,9 +125,9 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
     fun testDoesAppliesSplitShadeWeatherClockBlueprint() {
         testScope.runTest {
-            mSetFlagsRule.enableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
             val blueprint by collectLastValue(underTest.blueprint)
             whenever(clockController.config)
                 .thenReturn(
@@ -143,9 +147,9 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
     fun testAppliesWeatherClockBlueprint() {
         testScope.runTest {
-            mSetFlagsRule.enableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
             val blueprint by collectLastValue(underTest.blueprint)
             whenever(clockController.config)
                 .thenReturn(
@@ -163,4 +167,18 @@
             assertThat(blueprint?.id).isEqualTo(WEATHER_CLOCK_BLUEPRINT_ID)
         }
     }
+
+    @Test
+    @EnableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
+    fun testDoesNotApplySplitShadeBlueprint() {
+        testScope.runTest {
+            overrideResource(R.bool.config_use_split_notification_shade, true)
+            val blueprint by collectLastValue(underTest.blueprint)
+            clockRepository.setCurrentClock(clockController)
+            configurationRepository.onConfigurationChange()
+            runCurrent()
+
+            assertThat(blueprint?.id).isEqualTo(DefaultKeyguardBlueprint.DEFAULT)
+        }
+    }
 }
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/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
index f252163..0322301 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
@@ -33,6 +33,8 @@
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.plugins.clocks.ClockFaceConfig
 import com.android.systemui.plugins.clocks.ClockFaceController
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.shared.clocks.ClockRegistry
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
 import com.android.systemui.statusbar.policy.SplitShadeStateController
@@ -42,6 +44,7 @@
 import kotlin.test.Test
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestCoroutineScheduler
 import kotlinx.coroutines.test.TestScope
@@ -72,6 +75,7 @@
     @Mock private lateinit var splitShadeStateController: SplitShadeStateController
     @Mock private lateinit var notifsKeyguardInteractor: NotificationsKeyguardInteractor
     @Mock private lateinit var areNotificationsFullyHidden: Flow<Boolean>
+    @Mock private lateinit var shadeInteractor: ShadeInteractor
 
     @Before
     fun setup() {
@@ -96,13 +100,14 @@
         keyguardClockInteractor = KeyguardClockInteractor(keyguardClockRepository)
         whenever(notifsKeyguardInteractor.areNotificationsFullyHidden)
             .thenReturn(areNotificationsFullyHidden)
+        whenever(shadeInteractor.shadeMode).thenReturn(MutableStateFlow(ShadeMode.Single))
         underTest =
             KeyguardClockViewModel(
                 keyguardInteractor,
                 keyguardClockInteractor,
                 scope.backgroundScope,
-                splitShadeStateController,
-                notifsKeyguardInteractor
+                notifsKeyguardInteractor,
+                shadeInteractor,
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelWithKosmosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelWithKosmosTest.kt
new file mode 100644
index 0000000..e53cd11
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelWithKosmosTest.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardClockSwitch
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.keyguardClockRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardClockViewModelWithKosmosTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val underTest = kosmos.keyguardClockViewModel
+    private val testScope = kosmos.testScope
+
+    @Test
+    fun currentClockLayout_splitShadeOn_clockCentered_largeClock() =
+        testScope.runTest {
+            with(kosmos) {
+                shadeRepository.setShadeMode(ShadeMode.Split)
+                keyguardRepository.setClockShouldBeCentered(true)
+                keyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE)
+            }
+            val currentClockLayout by collectLastValue(underTest.currentClockLayout)
+            assertThat(currentClockLayout).isEqualTo(KeyguardClockViewModel.ClockLayout.LARGE_CLOCK)
+        }
+
+    @Test
+    fun currentClockLayout_splitShadeOn_clockNotCentered_largeClock_splitShadeLargeClock() =
+        testScope.runTest {
+            with(kosmos) {
+                shadeRepository.setShadeMode(ShadeMode.Split)
+                keyguardRepository.setClockShouldBeCentered(false)
+                keyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE)
+            }
+            val currentClockLayout by collectLastValue(underTest.currentClockLayout)
+            assertThat(currentClockLayout)
+                .isEqualTo(KeyguardClockViewModel.ClockLayout.SPLIT_SHADE_LARGE_CLOCK)
+        }
+
+    @Test
+    fun currentClockLayout_splitShadeOn_clockNotCentered_smallClock_splitShadeSmallClock() =
+        testScope.runTest {
+            with(kosmos) {
+                shadeRepository.setShadeMode(ShadeMode.Split)
+                keyguardRepository.setClockShouldBeCentered(false)
+                keyguardClockRepository.setClockSize(KeyguardClockSwitch.SMALL)
+            }
+            val currentClockLayout by collectLastValue(underTest.currentClockLayout)
+            assertThat(currentClockLayout)
+                .isEqualTo(KeyguardClockViewModel.ClockLayout.SPLIT_SHADE_SMALL_CLOCK)
+        }
+
+    @Test
+    fun currentClockLayout_singleShade_smallClock_smallClock() =
+        testScope.runTest {
+            with(kosmos) {
+                shadeRepository.setShadeMode(ShadeMode.Single)
+                keyguardClockRepository.setClockSize(KeyguardClockSwitch.SMALL)
+            }
+            val currentClockLayout by collectLastValue(underTest.currentClockLayout)
+            assertThat(currentClockLayout).isEqualTo(KeyguardClockViewModel.ClockLayout.SMALL_CLOCK)
+        }
+
+    @Test
+    fun currentClockLayout_singleShade_largeClock_largeClock() =
+        testScope.runTest {
+            with(kosmos) {
+                shadeRepository.setShadeMode(ShadeMode.Single)
+                keyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE)
+            }
+            val currentClockLayout by collectLastValue(underTest.currentClockLayout)
+            assertThat(currentClockLayout).isEqualTo(KeyguardClockViewModel.ClockLayout.LARGE_CLOCK)
+        }
+}
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/monet/ColorSchemeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.kt
new file mode 100644
index 0000000..261e8c0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.kt
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2021 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.monet
+
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import android.util.Log
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.theme.DynamicColors
+import com.google.ux.material.libmonet.hct.Hct
+import com.google.ux.material.libmonet.scheme.SchemeTonalSpot
+import java.io.File
+import java.io.FileWriter
+import java.io.StringWriter
+import javax.xml.parsers.DocumentBuilderFactory
+import javax.xml.transform.OutputKeys
+import javax.xml.transform.TransformerException
+import javax.xml.transform.TransformerFactory
+import javax.xml.transform.dom.DOMSource
+import javax.xml.transform.stream.StreamResult
+import kotlin.math.abs
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.w3c.dom.Document
+import org.w3c.dom.Element
+import org.w3c.dom.Node
+
+private const val fileHeader =
+    """
+  ~ 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.
+"""
+
+private fun testName(name: String): String {
+    return "Auto generated by: atest ColorSchemeTest#$name"
+}
+
+private const val commentRoles =
+    "Colors used in Android system, from design system. These " +
+        "values can be overlaid at runtime by OverlayManager RROs."
+
+private const val commentOverlay = "This value can be overlaid at runtime by OverlayManager RROs."
+
+private fun commentWhite(paletteName: String): String {
+    return "Lightest shade of the $paletteName color used by the system. White. $commentOverlay"
+}
+
+private fun commentBlack(paletteName: String): String {
+    return "Darkest shade of the $paletteName color used by the system. Black. $commentOverlay"
+}
+
+private fun commentShade(paletteName: String, tone: Int): String {
+    return "Shade of the $paletteName system color at $tone% perceptual luminance (L* in L*a*b* " +
+        "color space). $commentOverlay"
+}
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ColorSchemeTest : SysuiTestCase() {
+    @Test
+    fun generateThemeStyles() {
+        val document = buildDoc<Any>()
+
+        val themes = document.createElement("themes")
+        document.appendWithBreak(themes)
+
+        var hue = 0.0
+        while (hue < 360) {
+            val sourceColor = Hct.from(hue, 50.0, 50.0)
+            val sourceColorHex = sourceColor.toInt().toRGBHex()
+
+            val theme = document.createElement("theme")
+            theme.setAttribute("color", sourceColorHex)
+            themes.appendChild(theme)
+
+            for (styleValue in Style.entries) {
+                if (
+                    styleValue == Style.CLOCK ||
+                        styleValue == Style.CLOCK_VIBRANT ||
+                        styleValue == Style.CONTENT
+                ) {
+                    continue
+                }
+
+                val style = document.createElement(styleValue.name.lowercase())
+                val colorScheme = ColorScheme(sourceColor.toInt(), false, styleValue)
+
+                style.appendChild(
+                    document.createTextNode(
+                        listOf(
+                                colorScheme.accent1,
+                                colorScheme.accent2,
+                                colorScheme.accent3,
+                                colorScheme.neutral1,
+                                colorScheme.neutral2
+                            )
+                            .flatMap { a -> listOf(*a.allShades.toTypedArray()) }
+                            .joinToString(",", transform = Int::toRGBHex)
+                    )
+                )
+                theme.appendChild(style)
+            }
+
+            hue += 60
+        }
+
+        saveFile(document, "current_themes.xml")
+    }
+
+    @Test
+    fun generateDefaultValues() {
+        val document = buildDoc<Any>()
+
+        val resources = document.createElement("resources")
+        document.appendWithBreak(resources)
+
+        // shade colors
+        val colorScheme = ColorScheme(GOOGLE_BLUE, false)
+        arrayOf(
+                Triple("accent1", "Primary", colorScheme.accent1),
+                Triple("accent2", "Secondary", colorScheme.accent2),
+                Triple("accent3", "Tertiary", colorScheme.accent3),
+                Triple("neutral1", "Neutral", colorScheme.neutral1),
+                Triple("neutral2", "Secondary Neutral", colorScheme.neutral2)
+            )
+            .forEach {
+                val (paletteName, readable, palette) = it
+                palette.allShadesMapped.entries.forEachIndexed { index, (shade, colorValue) ->
+                    val comment =
+                        when (index) {
+                            0 -> commentWhite(readable)
+                            palette.allShadesMapped.entries.size - 1 -> commentBlack(readable)
+                            else -> commentShade(readable, abs(shade / 10 - 100))
+                        }
+                    resources.createColorEntry("system_${paletteName}_$shade", colorValue, comment)
+                }
+            }
+
+        resources.appendWithBreak(document.createComment(commentRoles), 2)
+
+        // dynamic colors
+        arrayOf(false, true).forEach { isDark ->
+            val suffix = if (isDark) "_dark" else "_light"
+            val dynamicScheme = SchemeTonalSpot(Hct.fromInt(GOOGLE_BLUE), isDark, 0.5)
+            DynamicColors.allDynamicColorsMapped(false).forEach {
+                resources.createColorEntry(
+                    "system_${it.first}$suffix",
+                    it.second.getArgb(dynamicScheme)
+                )
+            }
+        }
+
+        // fixed colors
+        val dynamicScheme = SchemeTonalSpot(Hct.fromInt(GOOGLE_BLUE), false, 0.5)
+        DynamicColors.getFixedColorsMapped(false).forEach {
+            resources.createColorEntry("system_${it.first}", it.second.getArgb(dynamicScheme))
+        }
+
+        saveFile(document, "role_values.xml")
+    }
+
+    // Helper Functions
+
+    private inline fun <reified T> buildDoc(): Document {
+        val functionName = T::class.simpleName + ""
+        val factory = DocumentBuilderFactory.newInstance()
+        val builder = factory.newDocumentBuilder()
+        val document = builder.newDocument()
+
+        document.appendWithBreak(document.createComment(fileHeader))
+        document.appendWithBreak(document.createComment(testName(functionName)))
+
+        return document
+    }
+
+    private fun documentToString(document: Document): String {
+        try {
+            val transformerFactory = TransformerFactory.newInstance()
+            val transformer = transformerFactory.newTransformer()
+            transformer.setOutputProperty(OutputKeys.MEDIA_TYPE, "application/xml")
+            transformer.setOutputProperty(OutputKeys.METHOD, "xml")
+            transformer.setOutputProperty(OutputKeys.INDENT, "yes")
+            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4")
+
+            val stringWriter = StringWriter()
+            transformer.transform(DOMSource(document), StreamResult(stringWriter))
+            return stringWriter.toString()
+        } catch (e: TransformerException) {
+            throw RuntimeException("Error transforming XML", e)
+        }
+    }
+
+    private fun saveFile(document: Document, fileName: String) {
+        val outPath = context.filesDir.path + "/" + fileName
+        Log.d("ColorSchemeXml", "Artifact $fileName created")
+        val writer = FileWriter(File(outPath))
+        writer.write(documentToString(document))
+        writer.close()
+    }
+}
+
+private fun Element.createColorEntry(name: String, value: Int, comment: String? = null) {
+    val doc = this.ownerDocument
+
+    if (comment != null) {
+        this.appendChild(doc.createComment(comment))
+    }
+
+    val color = doc.createElement("color")
+    this.appendChild(color)
+
+    color.setAttribute("name", name)
+    color.appendChild(doc.createTextNode("#" + value.toRGBHex()))
+}
+
+private fun Node.appendWithBreak(child: Node, lineBreaks: Int = 1): Node {
+    val doc = if (this is Document) this else this.ownerDocument
+    val node = doc.createTextNode("\n".repeat(lineBreaks))
+    this.appendChild(node)
+    return this.appendChild(child)
+}
+
+private fun Int.toRGBHex(): String {
+    return "%06X".format(0xFFFFFF and this)
+}
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/ActionProxyReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java
deleted file mode 100644
index 9ea30d6..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2020 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.screenshot;
-
-import static com.android.systemui.screenshot.ScreenshotController.ACTION_TYPE_SHARE;
-import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ID;
-import static com.android.systemui.screenshot.ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED;
-import static com.android.systemui.statusbar.phone.CentralSurfaces.SYSTEM_DIALOG_REASON_SCREENSHOT;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-
-import android.app.PendingIntent;
-import android.content.Intent;
-import android.os.Bundle;
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.settings.FakeDisplayTracker;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.stubbing.Answer;
-
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeoutException;
-
-@RunWith(AndroidTestingRunner.class)
-@SmallTest
-public class ActionProxyReceiverTest extends SysuiTestCase {
-    @Mock
-    private ActivityManagerWrapper mMockActivityManagerWrapper;
-    @Mock
-    private ScreenshotSmartActions mMockScreenshotSmartActions;
-    @Mock
-    private PendingIntent mMockPendingIntent;
-    @Mock
-    private ActivityStarter mActivityStarter;
-
-    private Intent mIntent;
-    private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
-
-    @Before
-    public void setup() throws InterruptedException, ExecutionException, TimeoutException {
-        MockitoAnnotations.initMocks(this);
-        mIntent = new Intent(mContext, ActionProxyReceiver.class)
-                .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, mMockPendingIntent);
-    }
-
-    @Test
-    public void testPendingIntentSentWithStatusBar() throws PendingIntent.CanceledException {
-        ActionProxyReceiver actionProxyReceiver = constructActionProxyReceiver();
-        // ensure that the pending intent call is passed through
-        doAnswer((Answer<Object>) invocation -> {
-            ((Runnable) invocation.getArgument(0)).run();
-            return null;
-        }).when(mActivityStarter).executeRunnableDismissingKeyguard(
-                any(Runnable.class), isNull(), anyBoolean(), anyBoolean(), anyBoolean());
-
-        actionProxyReceiver.onReceive(mContext, mIntent);
-
-        verify(mMockActivityManagerWrapper).closeSystemWindows(SYSTEM_DIALOG_REASON_SCREENSHOT);
-        verify(mActivityStarter).executeRunnableDismissingKeyguard(
-                any(Runnable.class), isNull(), eq(true), eq(true), eq(true));
-        verify(mMockPendingIntent).send(
-                eq(mContext), anyInt(), isNull(), isNull(), isNull(), isNull(), any(Bundle.class));
-    }
-
-    @Test
-    public void testSmartActionsNotNotifiedByDefault() {
-        ActionProxyReceiver actionProxyReceiver = constructActionProxyReceiver();
-
-        actionProxyReceiver.onReceive(mContext, mIntent);
-
-        verify(mMockScreenshotSmartActions, never())
-                .notifyScreenshotAction(anyString(), anyString(), anyBoolean(),
-                        any(Intent.class));
-    }
-
-    @Test
-    public void testSmartActionsNotifiedIfEnabled() {
-        ActionProxyReceiver actionProxyReceiver = constructActionProxyReceiver();
-        mIntent.putExtra(EXTRA_SMART_ACTIONS_ENABLED, true);
-        String testId = "testID";
-        mIntent.putExtra(EXTRA_ID, testId);
-
-        actionProxyReceiver.onReceive(mContext, mIntent);
-
-        verify(mMockScreenshotSmartActions).notifyScreenshotAction(
-                testId, ACTION_TYPE_SHARE, false, null);
-    }
-
-    private ActionProxyReceiver constructActionProxyReceiver() {
-        return new ActionProxyReceiver(
-                mMockActivityManagerWrapper,
-                mMockScreenshotSmartActions,
-                mDisplayTracker,
-                mActivityStarter
-        );
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DeleteScreenshotReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DeleteScreenshotReceiverTest.java
deleted file mode 100644
index d58f47a..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DeleteScreenshotReceiverTest.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2020 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.screenshot;
-
-import static com.android.systemui.screenshot.ScreenshotController.ACTION_TYPE_DELETE;
-import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ID;
-import static com.android.systemui.screenshot.ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED;
-import static com.android.systemui.screenshot.ScreenshotController.SCREENSHOT_URI_ID;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Intent;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Environment;
-import android.provider.MediaStore;
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.io.File;
-import java.util.concurrent.Executor;
-
-@RunWith(AndroidTestingRunner.class)
-@SmallTest
-public class DeleteScreenshotReceiverTest extends SysuiTestCase {
-
-    @Mock
-    private ScreenshotSmartActions mMockScreenshotSmartActions;
-    @Mock
-    private Executor mMockExecutor;
-
-    private DeleteScreenshotReceiver mDeleteScreenshotReceiver;
-    private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-        mDeleteScreenshotReceiver =
-                new DeleteScreenshotReceiver(mMockScreenshotSmartActions, mMockExecutor);
-    }
-
-    @Test
-    public void testNoUriProvided() {
-        Intent intent = new Intent(mContext, DeleteScreenshotReceiver.class);
-
-        mDeleteScreenshotReceiver.onReceive(mContext, intent);
-
-        verify(mMockExecutor, never()).execute(any(Runnable.class));
-        verify(mMockScreenshotSmartActions, never()).notifyScreenshotAction(
-                any(String.class), any(String.class), anyBoolean(),
-                any(Intent.class));
-    }
-
-    @Test
-    public void testFileDeleted() {
-        DeleteScreenshotReceiver deleteScreenshotReceiver =
-                new DeleteScreenshotReceiver(mMockScreenshotSmartActions, mFakeExecutor);
-        ContentResolver contentResolver = mContext.getContentResolver();
-        final Uri testUri = contentResolver.insert(
-                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, getFakeContentValues());
-        assertNotNull(testUri);
-
-        try {
-            Cursor cursor =
-                    contentResolver.query(testUri, null, null, null, null);
-            assertEquals(1, cursor.getCount());
-            Intent intent = new Intent(mContext, DeleteScreenshotReceiver.class)
-                    .putExtra(SCREENSHOT_URI_ID, testUri.toString());
-
-            deleteScreenshotReceiver.onReceive(mContext, intent);
-            int runCount = mFakeExecutor.runAllReady();
-
-            assertEquals(1, runCount);
-            cursor =
-                    contentResolver.query(testUri, null, null, null, null);
-            assertEquals(0, cursor.getCount());
-        } finally {
-            contentResolver.delete(testUri, null, null);
-        }
-
-        // ensure smart actions not called by default
-        verify(mMockScreenshotSmartActions, never()).notifyScreenshotAction(
-                any(String.class), any(String.class), anyBoolean(), any(Intent.class));
-    }
-
-    @Test
-    public void testNotifyScreenshotAction() {
-        Intent intent = new Intent(mContext, DeleteScreenshotReceiver.class);
-        String uriString = "testUri";
-        String testId = "testID";
-        intent.putExtra(SCREENSHOT_URI_ID, uriString);
-        intent.putExtra(EXTRA_ID, testId);
-        intent.putExtra(EXTRA_SMART_ACTIONS_ENABLED, true);
-
-        mDeleteScreenshotReceiver.onReceive(mContext, intent);
-
-        verify(mMockExecutor).execute(any(Runnable.class));
-        verify(mMockScreenshotSmartActions).notifyScreenshotAction(testId,
-                ACTION_TYPE_DELETE, false, null);
-    }
-
-    private static ContentValues getFakeContentValues() {
-        final ContentValues values = new ContentValues();
-        values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES
-                + File.separator + Environment.DIRECTORY_SCREENSHOTS);
-        values.put(MediaStore.MediaColumns.DISPLAY_NAME, "test_screenshot");
-        values.put(MediaStore.MediaColumns.MIME_TYPE, "image/png");
-        values.put(MediaStore.MediaColumns.DATE_ADDED, 0);
-        values.put(MediaStore.MediaColumns.DATE_MODIFIED, 0);
-        return values;
-    }
-}
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/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/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/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
index f89b9cd..66a306e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
@@ -53,8 +53,6 @@
 import com.android.internal.logging.UiEventLogger.UiEventEnum
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.broadcast.FakeBroadcastDispatcher
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogcatEchoTracker
 import com.android.systemui.log.core.LogLevel
@@ -150,7 +148,10 @@
 
     @Before
     fun setUp() {
-        val user = UserInfo(ActivityManager.getCurrentUser(), "Current user", /* flags = */ 0)
+        val userId = ActivityManager.getCurrentUser()
+        val user = UserInfo(userId, "Current user", /* flags = */ 0)
+
+        deviceProvisionedController.currentUser = userId
         userTracker.set(listOf(user), /* currentUserIndex = */ 0)
 
         provider.start()
@@ -823,6 +824,13 @@
     }
 
     @Test
+    fun testShouldFsi_userSetupIncomplete() {
+        ensureUserSetupIncompleteFsiState()
+        assertShouldFsi(buildFsiEntry())
+        assertNoEventsLogged()
+    }
+
+    @Test
     fun testShouldNotFsi_noHunOrKeyguard() {
         ensureNoHunOrKeyguardFsiState()
         assertShouldNotFsi(buildFsiEntry())
@@ -888,7 +896,8 @@
         var statusBarState: Int? = null,
         var keyguardIsShowing: Boolean = false,
         var keyguardIsOccluded: Boolean = false,
-        var deviceProvisioned: Boolean = true
+        var deviceProvisioned: Boolean = true,
+        var currentUserSetup: Boolean = true
     )
 
     protected fun setState(state: State): Unit =
@@ -925,6 +934,7 @@
             keyguardStateController.isShowing = keyguardIsShowing
 
             deviceProvisionedController.deviceProvisioned = deviceProvisioned
+            deviceProvisionedController.isCurrentUserSetup = currentUserSetup
         }
 
     protected fun ensureState(block: State.() -> Unit) =
@@ -999,6 +1009,18 @@
         hunSettingEnabled = false
         keyguardIsShowing = false
         deviceProvisioned = false
+        currentUserSetup = true
+        run(block)
+    }
+
+    protected fun ensureUserSetupIncompleteFsiState(block: State.() -> Unit = {}) = ensureState {
+        isInteractive = true
+        isDreaming = false
+        statusBarState = SHADE
+        hunSettingEnabled = false
+        keyguardIsShowing = false
+        deviceProvisioned = true
+        currentUserSetup = false
         run(block)
     }
 
@@ -1009,6 +1031,7 @@
         hunSettingEnabled = false
         keyguardIsShowing = false
         deviceProvisioned = true
+        currentUserSetup = true
         run(block)
     }
 
@@ -1216,7 +1239,7 @@
 
                     neb.setImportance(importance)
                     val channel =
-                            NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance)
+                        NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance)
                     channel.isImportantConversation = isImportantConversation
                     neb.setChannel(channel)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index ea4ae17..d5c4053 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,8 +49,8 @@
 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;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -78,6 +78,7 @@
     @Mock private CommandQueue mCommandQueue;
     @Mock private QuickSettingsController mQuickSettingsController;
     @Mock private ShadeViewController mShadeViewController;
+    @Mock private PanelExpansionInteractor mPanelExpansionInteractor;
     @Mock private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
     private final MetricsLogger mMetricsLogger = new FakeMetricsLogger();
     @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -112,6 +113,7 @@
                 mShadeController,
                 mCommandQueue,
                 mShadeViewController,
+                mPanelExpansionInteractor,
                 mRemoteInputQuickSettingsDisabler,
                 mMetricsLogger,
                 mKeyguardUpdateMonitor,
@@ -126,7 +128,6 @@
                 mStatusBarHideIconsForBouncerManager,
                 mPowerManager,
                 Optional.of(mVibrator),
-                new DisableFlagsLogger(),
                 DEFAULT_DISPLAY,
                 mCameraLauncherLazy,
                 mUserTracker,
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/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index 3bf54a3..de18913 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
@@ -27,6 +27,7 @@
 import com.android.systemui.keyguard.KeyguardViewMediator
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
 import com.android.systemui.statusbar.LightRevealScrim
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.StatusBarStateControllerImpl
@@ -67,6 +68,8 @@
     @Mock
     private lateinit var shadeViewController: ShadeViewController
     @Mock
+    private lateinit var panelExpansionInteractor: PanelExpansionInteractor
+    @Mock
     private lateinit var notifShadeWindowController: NotificationShadeWindowController
     @Mock
     private lateinit var lightRevealScrim: LightRevealScrim
@@ -87,23 +90,20 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         controller = UnlockedScreenOffAnimationController(
-                context,
-                wakefulnessLifecycle,
-                statusBarStateController,
-                { keyguardViewMediator },
-                keyguardStateController,
-                { dozeParameters },
-                globalSettings,
-                { notifShadeWindowController },
-                interactionJankMonitor,
-                powerManager,
-                handler = handler
+            context,
+            wakefulnessLifecycle,
+            statusBarStateController,
+            { keyguardViewMediator },
+            keyguardStateController,
+            { dozeParameters },
+            globalSettings,
+            { notifShadeWindowController },
+            interactionJankMonitor,
+            powerManager,
+            { panelExpansionInteractor },
+            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/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index ab28a2f..ed7c956 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -985,7 +985,7 @@
         FabricatedOverlay neutrals = overlays[1];
         FabricatedOverlay dynamic = overlays[2];
 
-        final int colorsPerPalette = 12;
+        final int colorsPerPalette = 13;
 
         // Color resources were added for all 3 accent palettes
         verify(accents, times(colorsPerPalette * 3))
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/flags/EnableSceneContainer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
index cceb3ff..f65c74f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
@@ -19,6 +19,7 @@
 import android.platform.test.annotations.EnableFlags
 import com.android.systemui.Flags.FLAG_COMPOSE_LOCKSCREEN
 import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
+import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
 import com.android.systemui.Flags.FLAG_MEDIA_IN_SCENE_CONTAINER
 import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
 import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
@@ -33,6 +34,7 @@
     FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
     FLAG_COMPOSE_LOCKSCREEN,
     FLAG_MEDIA_IN_SCENE_CONTAINER,
+    FLAG_KEYGUARD_WM_STATE_REFACTOR,
 )
 @Retention(AnnotationRetention.RUNTIME)
 @Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
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/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
index d84988d..29167d6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
@@ -27,6 +27,7 @@
             surfaceBehindInteractor = keyguardSurfaceBehindInteractor,
             fromLockscreenInteractor = fromLockscreenTransitionInteractor,
             fromBouncerInteractor = fromPrimaryBouncerTransitionInteractor,
+            fromAlternateBouncerInteractor = fromAlternateBouncerTransitionInteractor,
             notificationLaunchAnimationInteractor = notificationLaunchAnimationInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
index 4a85909..60dd48a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
@@ -20,8 +20,8 @@
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
-import com.android.systemui.statusbar.policy.splitShadeStateController
 
 val Kosmos.keyguardClockViewModel by
     Kosmos.Fixture {
@@ -29,7 +29,7 @@
             keyguardInteractor = keyguardInteractor,
             keyguardClockInteractor = keyguardClockInteractor,
             applicationScope = applicationCoroutineScope,
-            splitShadeStateController = splitShadeStateController,
             notifsKeyguardInteractor = notificationsKeyguardInteractor,
+            shadeInteractor = shadeInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
index 8da5dd4..f0fedd2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
@@ -20,7 +20,7 @@
 import com.android.systemui.keyguard.domain.interactor.keyguardBlueprintInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.statusbar.policy.splitShadeStateController
+import com.android.systemui.shade.domain.interactor.shadeInteractor
 
 val Kosmos.lockscreenContentViewModel by
     Kosmos.Fixture {
@@ -29,6 +29,6 @@
             interactor = keyguardBlueprintInteractor,
             authController = authController,
             longPress = keyguardLongPressViewModel,
-            splitShadeStateController = splitShadeStateController,
+            shadeInteractor = shadeInteractor,
         )
     }
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/statusbar/domain/interactor/KeyguardViewOcclusionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractorKosmos.kt
similarity index 77%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardViewOcclusionInteractorKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractorKosmos.kt
index 9e34fe8..2ed56ce 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardViewOcclusionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractorKosmos.kt
@@ -16,7 +16,9 @@
 
 package com.android.systemui.statusbar.domain.interactor
 
+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.kosmos.Kosmos
 import com.android.systemui.power.domain.interactor.powerInteractor
 
@@ -26,5 +28,7 @@
             keyguardTransitionInteractor = this.keyguardTransitionInteractor,
             keyguardOcclusionInteractor = this.keyguardOcclusionInteractor,
             powerInteractor = this.powerInteractor,
+            wmLockscreenVisibilityInteractor = windowManagerLockscreenVisibilityInteractor,
+            surfaceBehindInteractor = keyguardSurfaceBehindInteractor,
         )
     }
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/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/Android.bp b/services/Android.bp
index 8709692..98a7979 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -119,6 +119,7 @@
         ":services.companion-sources",
         ":services.contentcapture-sources",
         ":services.contentsuggestions-sources",
+        ":services.contextualsearch-sources",
         ":services.coverage-sources",
         ":services.credentials-sources",
         ":services.devicepolicy-sources",
@@ -208,6 +209,7 @@
         "services.companion",
         "services.contentcapture",
         "services.contentsuggestions",
+        "services.contextualsearch",
         "services.coverage",
         "services.credentials",
         "services.devicepolicy",
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 4be303a..4e14dee 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -935,34 +935,11 @@
                     }
                     final AccessibilityUserState userState = getUserStateLocked(userId);
 
-                    if (Flags.disableContinuousShortcutOnForceStop()) {
-                        if (doit && onPackagesForceStoppedLocked(packages, userState)) {
-                            onUserStateChangedLocked(userState);
-                            return false;
-                        } else {
-                            return true;
-                        }
-                    } else {
-                        final Iterator<ComponentName> it = userState.mEnabledServices.iterator();
-                        while (it.hasNext()) {
-                            final ComponentName comp = it.next();
-                            final String compPkg = comp.getPackageName();
-                            for (String pkg : packages) {
-                                if (compPkg.equals(pkg)) {
-                                    if (!doit) {
-                                        return true;
-                                    }
-                                    it.remove();
-                                    userState.getBindingServicesLocked().remove(comp);
-                                    userState.getCrashedServicesLocked().remove(comp);
-                                    persistComponentNamesToSettingLocked(
-                                            Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
-                                            userState.mEnabledServices, userId);
-                                    onUserStateChangedLocked(userState);
-                                }
-                            }
-                        }
+                    if (doit && onPackagesForceStoppedLocked(packages, userState)) {
+                        onUserStateChangedLocked(userState);
                         return false;
+                    } else {
+                        return true;
                     }
                 }
             }
@@ -1706,20 +1683,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 +3719,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 +3775,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 +5202,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 +5255,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 +5276,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/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 a55b8d0..d006cf6 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -170,8 +170,8 @@
 import android.util.SparseArray;
 import android.util.TimeUtils;
 import android.view.KeyEvent;
-import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillFeatureFlags;
+import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
 import android.view.autofill.AutofillManager.AutofillCommitReason;
 import android.view.autofill.AutofillManager.SmartSuggestionMode;
@@ -597,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
@@ -4390,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);
@@ -4459,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(
@@ -4510,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();
@@ -4588,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:
@@ -5327,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);
@@ -5341,6 +5365,8 @@
                 Slog.v(TAG, "Changed state of " + id + " to " + viewState.getStateAsString());
             }
         }
+        mPresentationStatsEventLogger.maybeSetViewFillFailureCounts(ids.size());
+        mPresentationStatsEventLogger.logAndEndEvent();
     }
 
     @GuardedBy("mLock")
@@ -6526,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/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index ff07619..9e3f5ce 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -18,6 +18,7 @@
 
 import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
@@ -48,6 +49,8 @@
 import com.android.modules.expresslog.Counter;
 
 import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 /**
  * A controller to control the policies of the windows that can be displayed on the virtual display.
@@ -121,8 +124,12 @@
     private final ComponentName mPermissionDialogComponent;
     private final Object mGenericWindowPolicyControllerLock = new Object();
     @Nullable private final ActivityBlockedCallback mActivityBlockedCallback;
+
+    // Do not access mDisplayId and mIsMirrorDisplay directly, instead use waitAndGetDisplayId()
+    // and waitAndGetIsMirrorDisplay()
     private int mDisplayId = Display.INVALID_DISPLAY;
     private boolean mIsMirrorDisplay = false;
+    private final CountDownLatch mDisplayIdSetLatch = new CountDownLatch(1);
 
     @NonNull
     @GuardedBy("mGenericWindowPolicyControllerLock")
@@ -214,6 +221,33 @@
     void setDisplayId(int displayId, boolean isMirrorDisplay) {
         mDisplayId = displayId;
         mIsMirrorDisplay = isMirrorDisplay;
+        mDisplayIdSetLatch.countDown();
+    }
+
+    private int waitAndGetDisplayId() {
+        try {
+            if (!mDisplayIdSetLatch.await(10, TimeUnit.SECONDS)) {
+                Slog.e(TAG, "Timed out while waiting for GWPC displayId to be set.");
+                return INVALID_DISPLAY;
+            }
+        } catch (InterruptedException e) {
+            Slog.e(TAG, "Interrupted while waiting for GWPC displayId to be set.");
+            return INVALID_DISPLAY;
+        }
+        return mDisplayId;
+    }
+
+    private boolean waitAndGetIsMirrorDisplay() {
+        try {
+            if (!mDisplayIdSetLatch.await(10, TimeUnit.SECONDS)) {
+                Slog.e(TAG, "Timed out while waiting for GWPC isMirrorDisplay to be set.");
+                return false;
+            }
+        } catch (InterruptedException e) {
+            Slog.e(TAG, "Interrupted while waiting for GWPC isMirrorDisplay to be set.");
+            return false;
+        }
+        return mIsMirrorDisplay;
     }
 
     /**
@@ -281,7 +315,7 @@
             @WindowConfiguration.WindowingMode int windowingMode, int launchingFromDisplayId,
             boolean isNewTask) {
         // Mirror displays cannot contain activities.
-        if (mIsMirrorDisplay) {
+        if (waitAndGetIsMirrorDisplay()) {
             Slog.d(TAG, "Mirror virtual displays cannot contain activities.");
             return false;
         }
@@ -341,11 +375,13 @@
     @SuppressWarnings("AndroidFrameworkRequiresPermission")
     public boolean keepActivityOnWindowFlagsChanged(ActivityInfo activityInfo, int windowFlags,
             int systemWindowFlags) {
+        int displayId = waitAndGetDisplayId();
         // The callback is fired only when windowFlags are changed. To let VirtualDevice owner
         // aware that the virtual display has a secure window on top.
-        if ((windowFlags & FLAG_SECURE) != 0 && mSecureWindowCallback != null) {
+        if ((windowFlags & FLAG_SECURE) != 0 && mSecureWindowCallback != null
+                && displayId != INVALID_DISPLAY) {
             // Post callback on the main thread, so it doesn't block activity launching.
-            mHandler.post(() -> mSecureWindowCallback.onSecureWindowShown(mDisplayId,
+            mHandler.post(() -> mSecureWindowCallback.onSecureWindowShown(displayId,
                     activityInfo.applicationInfo.uid));
         }
 
@@ -365,13 +401,14 @@
 
     @Override
     public void onTopActivityChanged(ComponentName topActivity, int uid, @UserIdInt int userId) {
+        int displayId = waitAndGetDisplayId();
         // Don't send onTopActivityChanged() callback when topActivity is null because it's defined
         // as @NonNull in ActivityListener interface. Sends onDisplayEmpty() callback instead when
         // there is no activity running on virtual display.
-        if (mActivityListener != null && topActivity != null) {
+        if (mActivityListener != null && topActivity != null && displayId != INVALID_DISPLAY) {
             // Post callback on the main thread so it doesn't block activity launching
             mHandler.post(() ->
-                    mActivityListener.onTopActivityChanged(mDisplayId, topActivity, userId));
+                    mActivityListener.onTopActivityChanged(displayId, topActivity, userId));
         }
     }
 
@@ -380,9 +417,11 @@
         synchronized (mGenericWindowPolicyControllerLock) {
             mRunningUids.clear();
             mRunningUids.addAll(runningUids);
-            if (mActivityListener != null && mRunningUids.isEmpty()) {
+            int displayId = waitAndGetDisplayId();
+            if (mActivityListener != null && mRunningUids.isEmpty()
+                    && displayId != INVALID_DISPLAY) {
                 // Post callback on the main thread so it doesn't block activity launching
-                mHandler.post(() -> mActivityListener.onDisplayEmpty(mDisplayId));
+                mHandler.post(() -> mActivityListener.onDisplayEmpty(displayId));
             }
             if (!mRunningAppsChangedListeners.isEmpty()) {
                 final ArraySet<RunningAppsChangedListener> listeners =
@@ -438,10 +477,12 @@
     }
 
     private void notifyActivityBlocked(ActivityInfo activityInfo) {
+        int displayId = waitAndGetDisplayId();
         // Don't trigger activity blocked callback for mirror displays, because we can't show
         // any activity or presentation on it anyway.
-        if (!mIsMirrorDisplay && mActivityBlockedCallback != null) {
-            mActivityBlockedCallback.onActivityBlocked(mDisplayId, activityInfo);
+        if (!waitAndGetIsMirrorDisplay() && mActivityBlockedCallback != null
+                && displayId != INVALID_DISPLAY) {
+            mActivityBlockedCallback.onActivityBlocked(displayId, activityInfo);
         }
         if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
             Counter.logIncrementWithUid(
diff --git a/services/contextualsearch/Android.bp b/services/contextualsearch/Android.bp
new file mode 100644
index 0000000..b4dd20e
--- /dev/null
+++ b/services/contextualsearch/Android.bp
@@ -0,0 +1,22 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+    name: "services.contextualsearch-sources",
+    srcs: ["java/**/*.java"],
+    path: "java",
+    visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+    name: "services.contextualsearch",
+    defaults: ["platform_service_defaults"],
+    srcs: [":services.contextualsearch-sources"],
+    libs: ["services.core"],
+}
diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
new file mode 100644
index 0000000..b28bc1f
--- /dev/null
+++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
@@ -0,0 +1,315 @@
+/*
+ * 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.contextualsearch;
+
+import static android.Manifest.permission.ACCESS_CONTEXTUAL_SEARCH;
+import static android.content.Context.CONTEXTUAL_SEARCH_SERVICE;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
+import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
+import static android.content.pm.PackageManager.MATCH_FACTORY_ONLY;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.app.ActivityOptions;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.app.contextualsearch.ContextualSearchManager;
+import android.app.contextualsearch.ContextualSearchState;
+import android.app.contextualsearch.IContextualSearchCallback;
+import android.app.contextualsearch.IContextualSearchManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.graphics.Bitmap;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelableException;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.util.Log;
+import android.util.Slog;
+import android.window.ScreenCapture;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.wm.ActivityAssistInfo;
+import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.server.wm.WindowManagerInternal;
+
+import java.io.FileDescriptor;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public class ContextualSearchManagerService extends SystemService {
+
+    private static final int MSG_RESET_TEMPORARY_PACKAGE = 0;
+    private static final int MAX_TEMP_PACKAGE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
+    private final Context mContext;
+    private final ActivityTaskManagerInternal mAtmInternal;
+    private final WindowManagerInternal mWmInternal;
+    private final DevicePolicyManagerInternal mDpmInternal;
+
+    private Handler mTemporaryHandler;
+
+    @GuardedBy("this")
+    private String mTemporaryPackage = null;
+    private static final String TAG = ContextualSearchManagerService.class.getSimpleName();
+
+    public ContextualSearchManagerService(@NonNull Context context) {
+        super(context);
+        if (DEBUG_USER) Log.d(TAG, "ContextualSearchManagerService created");
+        mContext = context;
+        mAtmInternal = Objects.requireNonNull(
+                LocalServices.getService(ActivityTaskManagerInternal.class));
+        mWmInternal = Objects.requireNonNull(LocalServices.getService(WindowManagerInternal.class));
+        mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class);
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService(CONTEXTUAL_SEARCH_SERVICE, new ContextualSearchManagerStub());
+    }
+
+    void resetTemporaryPackage() {
+        synchronized (this) {
+            enforceOverridingPermission("resetTemporaryPackage");
+            if (mTemporaryHandler != null) {
+                mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_PACKAGE);
+                mTemporaryHandler = null;
+            }
+            if (DEBUG_USER) Log.d(TAG, "mTemporaryPackage reset.");
+            mTemporaryPackage = null;
+        }
+    }
+
+    void setTemporaryPackage(@NonNull String temporaryPackage, int durationMs) {
+        synchronized (this) {
+            enforceOverridingPermission("setTemporaryPackage");
+            final int maxDurationMs = MAX_TEMP_PACKAGE_DURATION_MS;
+            if (durationMs > maxDurationMs) {
+                throw new IllegalArgumentException(
+                        "Max duration is " + maxDurationMs + " (called with " + durationMs + ")");
+            }
+            if (mTemporaryHandler == null) {
+                mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) {
+                    @Override
+                    public void handleMessage(Message msg) {
+                        if (msg.what == MSG_RESET_TEMPORARY_PACKAGE) {
+                            synchronized (this) {
+                                resetTemporaryPackage();
+                            }
+                        } else {
+                            Slog.wtf(TAG, "invalid handler msg: " + msg);
+                        }
+                    }
+                };
+            } else {
+                mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_PACKAGE);
+            }
+            mTemporaryPackage = temporaryPackage;
+            mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_PACKAGE, durationMs);
+            if (DEBUG_USER) Log.d(TAG, "mTemporaryPackage set to " + mTemporaryPackage);
+        }
+    }
+
+    private Intent getResolvedLaunchIntent() {
+        synchronized (this) {
+            // If mTemporaryPackage is not null, use it to get the ContextualSearch intent.
+            String csPkgName = mTemporaryPackage != null ? mTemporaryPackage : mContext
+                    .getResources().getString(R.string.config_defaultContextualSearchPackageName);
+            if (csPkgName.isEmpty()) {
+                // Return null if csPackageName is not specified.
+                return null;
+            }
+            Intent launchIntent = new Intent(
+                    ContextualSearchManager.ACTION_LAUNCH_CONTEXTUAL_SEARCH);
+            launchIntent.setPackage(csPkgName);
+            ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivity(
+                    launchIntent, MATCH_FACTORY_ONLY);
+            if (resolveInfo == null) {
+                return null;
+            }
+            ComponentName componentName = resolveInfo.getComponentInfo().getComponentName();
+            if (componentName == null) {
+                return null;
+            }
+            launchIntent.setComponent(componentName);
+            return launchIntent;
+        }
+    }
+
+    private Intent getContextualSearchIntent(int entrypoint, IBinder mToken) {
+        final Intent launchIntent = getResolvedLaunchIntent();
+        if (launchIntent == null) {
+            return null;
+        }
+
+        if (DEBUG_USER) Log.d(TAG, "Launch component: " + launchIntent.getComponent());
+        launchIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NO_ANIMATION
+                | FLAG_ACTIVITY_NO_USER_ACTION);
+        launchIntent.putExtra(ContextualSearchManager.EXTRA_ENTRYPOINT, entrypoint);
+        launchIntent.putExtra(ContextualSearchManager.EXTRA_TOKEN, mToken);
+        boolean isAssistDataAllowed = mAtmInternal.isAssistDataAllowed();
+        final List<ActivityAssistInfo> records = mAtmInternal.getTopVisibleActivities();
+        ArrayList<String> visiblePackageNames = new ArrayList<>();
+        boolean isManagedProfileVisible = false;
+        for (ActivityAssistInfo record : records) {
+            // Add the package name to the list only if assist data is allowed.
+            if (isAssistDataAllowed) {
+                visiblePackageNames.add(record.getComponentName().getPackageName());
+            }
+            if (mDpmInternal != null
+                    && mDpmInternal.isUserOrganizationManaged(record.getUserId())) {
+                isManagedProfileVisible = true;
+            }
+        }
+        final ScreenCapture.ScreenshotHardwareBuffer shb;
+        if (mWmInternal != null) {
+            shb = mWmInternal.takeAssistScreenshot();
+        } else {
+            shb = null;
+        }
+        final Bitmap bm = shb != null ? shb.asBitmap() : null;
+        // Now that everything is fetched, putting it in the launchIntent.
+        if (bm != null) {
+            launchIntent.putExtra(ContextualSearchManager.EXTRA_FLAG_SECURE_FOUND,
+                    shb.containsSecureLayers());
+            // Only put the screenshot if assist data is allowed
+            if (isAssistDataAllowed) {
+                launchIntent.putExtra(ContextualSearchManager.EXTRA_SCREENSHOT, bm.asShared());
+            }
+        }
+        launchIntent.putExtra(ContextualSearchManager.EXTRA_IS_MANAGED_PROFILE_VISIBLE,
+                isManagedProfileVisible);
+        // Only put the list of visible package names if assist data is allowed
+        if (isAssistDataAllowed) {
+            launchIntent.putExtra(ContextualSearchManager.EXTRA_VISIBLE_PACKAGE_NAMES,
+                    visiblePackageNames);
+        }
+        return launchIntent;
+    }
+
+    @RequiresPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS)
+    private int invokeContextualSearchIntent(Intent launchIntent) {
+        // Contextual search starts with a frozen screen - so we launch without
+        // any system animations or starting window.
+        final ActivityOptions opts = ActivityOptions.makeCustomTaskAnimation(mContext,
+                /* enterResId= */ 0, /* exitResId= */ 0, null, null, null);
+        opts.setDisableStartingWindow(true);
+        return mAtmInternal.startActivityWithScreenshot(launchIntent,
+                mContext.getPackageName(), Binder.getCallingUid(), Binder.getCallingPid(), null,
+                opts.toBundle(), Binder.getCallingUserHandle().getIdentifier());
+    }
+
+    private void enforcePermission(@NonNull final String func) {
+        Context ctx = getContext();
+        if (!(ctx.checkCallingPermission(ACCESS_CONTEXTUAL_SEARCH) == PERMISSION_GRANTED
+                || isCallerTemporary())) {
+            String msg = "Permission Denial: Cannot call " + func + " from pid="
+                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid();
+            throw new SecurityException(msg);
+        }
+    }
+
+    private void enforceOverridingPermission(@NonNull final String func) {
+        if (!(Binder.getCallingUid() == Process.SHELL_UID
+                || Binder.getCallingUid() == Process.ROOT_UID
+                || Binder.getCallingUid() == Process.SYSTEM_UID)) {
+            String msg = "Permission Denial: Cannot override Contextual Search. Called " + func
+                    + " from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid();
+            throw new SecurityException(msg);
+        }
+    }
+
+    private boolean isCallerTemporary() {
+        synchronized (this) {
+            return mTemporaryPackage != null
+                    && mTemporaryPackage.equals(
+                    getContext().getPackageManager().getNameForUid(Binder.getCallingUid()));
+        }
+    }
+
+    private class ContextualSearchManagerStub extends IContextualSearchManager.Stub {
+        private @Nullable IBinder mToken;
+
+        @Override
+        public void startContextualSearch(int entrypoint) {
+            synchronized (this) {
+                if (DEBUG_USER) Log.d(TAG, "startContextualSearch");
+                enforcePermission("startContextualSearch");
+                mToken = new Binder();
+                // We get the launch intent with the system server's identity because the system
+                // server has READ_FRAME_BUFFER permission to get the screenshot and because only
+                // the system server can invoke non-exported activities.
+                Binder.withCleanCallingIdentity(() -> {
+                    Intent launchIntent = getContextualSearchIntent(entrypoint, mToken);
+                    if (launchIntent != null) {
+                        int result = invokeContextualSearchIntent(launchIntent);
+                        if (DEBUG_USER) Log.d(TAG, "Launch result: " + result);
+                    }
+                });
+            }
+        }
+
+        @Override
+        public void getContextualSearchState(
+                @NonNull IBinder token,
+                @NonNull IContextualSearchCallback callback) {
+            if (DEBUG_USER) {
+                Log.i(TAG, "getContextualSearchState token: " + token + ", callback: " + callback);
+            }
+            if (mToken == null || !mToken.equals(token)) {
+                if (DEBUG_USER) {
+                    Log.e(TAG, "getContextualSearchState: invalid token, returning error");
+                }
+                try {
+                    callback.onError(
+                            new ParcelableException(new IllegalArgumentException("Invalid token")));
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Could not invoke onError callback", e);
+                }
+                return;
+            }
+            mToken = null;
+            // Process data request
+            try {
+                callback.onResult(new ContextualSearchState(null, null, Bundle.EMPTY));
+            } catch (RemoteException e) {
+                Log.e(TAG, "Could not invoke onResult callback", e);
+            }
+        }
+
+        public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+                @Nullable FileDescriptor err, @NonNull String[] args,
+                @Nullable ShellCallback callback, @NonNull ResultReceiver resultReceiver) {
+            new ContextualSearchManagerShellCommand(ContextualSearchManagerService.this)
+                    .exec(this, in, out, err, args, callback, resultReceiver);
+        }
+    }
+}
diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerShellCommand.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerShellCommand.java
new file mode 100644
index 0000000..5777e1d
--- /dev/null
+++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerShellCommand.java
@@ -0,0 +1,78 @@
+/*
+ * 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.contextualsearch;
+
+import android.annotation.NonNull;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+public class ContextualSearchManagerShellCommand extends ShellCommand {
+
+    private final ContextualSearchManagerService mService;
+
+    ContextualSearchManagerShellCommand(@NonNull ContextualSearchManagerService service) {
+        mService = service;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(cmd);
+        }
+        final PrintWriter pw = getOutPrintWriter();
+        switch (cmd) {
+            case "set": {
+                final String what = getNextArgRequired();
+                switch (what) {
+                    case "temporary-package": {
+                        String packageName = getNextArg();
+                        if (packageName == null) {
+                            mService.resetTemporaryPackage();
+                            pw.println("ContextualSearchManagerService reset.");
+                            return 0;
+                        }
+                        final int duration = Integer.parseInt(getNextArgRequired());
+                        mService.setTemporaryPackage(packageName, duration);
+                        pw.println("ContextualSearchManagerService temporarily set to "
+                                + packageName + " for " + duration + "ms");
+                        break;
+                    }
+                }
+            }
+            break;
+            default:
+                return handleDefaultCommands(cmd);
+        }
+        return 0;
+    }
+
+    @Override
+    public void onHelp() {
+        try (PrintWriter pw = getOutPrintWriter()) {
+            pw.println("ContextualSearchService commands:");
+            pw.println("  help");
+            pw.println("    Prints this help text.");
+            pw.println("");
+            pw.println("  set temporary-package [PACKAGE_NAME DURATION]");
+            pw.println("    Temporarily (for DURATION ms) changes the Contextual Search "
+                    + "implementation.");
+            pw.println("    To reset, call without any arguments.");
+            pw.println("");
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 0012b3d..133a77d 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -4658,6 +4658,8 @@
                         INVALID_UID /* sdkSandboxClientAppUid */,
                         null /* sdkSandboxClientAppPackage */,
                         false /* inSharedIsolatedProcess */);
+                r.foregroundId = fgsDelegateOptions.mClientNotificationId;
+                r.foregroundNoti = fgsDelegateOptions.mClientNotification;
                 res.setService(r);
                 smap.mServicesByInstanceName.put(cn, r);
                 smap.mServicesByIntent.put(filter, r);
@@ -8171,7 +8173,7 @@
      * @param targetProcess  the process of the service to start.
      * @return {@link ReasonCode}
      */
-    private @ReasonCode int shouldAllowFgsWhileInUsePermissionLocked(String callingPackage,
+    @ReasonCode int shouldAllowFgsWhileInUsePermissionLocked(String callingPackage,
             int callingPid, int callingUid, @Nullable ProcessRecord targetProcess,
             BackgroundStartPrivileges backgroundStartPrivileges) {
         int ret = REASON_DENIED;
@@ -9046,6 +9048,10 @@
             });
         }
         signalForegroundServiceObserversLocked(r);
+        if (r.foregroundId != 0 && r.foregroundNoti != null) {
+            r.foregroundNoti.flags |= Notification.FLAG_FOREGROUND_SERVICE;
+            r.postNotification(true);
+        }
         return true;
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index ff83797..272e84b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -51,6 +51,7 @@
 import android.util.ArraySet;
 import android.util.KeyValueListParser;
 import android.util.Slog;
+import android.util.SparseBooleanArray;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
@@ -436,6 +437,18 @@
     private static final String KEY_MAX_SERVICE_CONNECTIONS_PER_PROCESS =
             "max_service_connections_per_process";
 
+    private static final String KEY_PROC_STATE_DEBUG_UIDS = "proc_state_debug_uids";
+
+    /**
+     * UIDs we want to print detailed info in OomAdjuster.
+     * It's only used for debugging, and it's almost never updated, so we just create a new
+     * array when it's changed to avoid synchronization.
+     */
+    volatile SparseBooleanArray mProcStateDebugUids = new SparseBooleanArray(0);
+    volatile boolean mEnableProcStateStacktrace = false;
+    volatile int mProcStateDebugSetProcStateDelay = 0;
+    volatile int mProcStateDebugSetUidStateDelay = 0;
+
     // Maximum number of cached processes we will allow.
     public int MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES;
 
@@ -1339,6 +1352,9 @@
                             case KEY_PSS_TO_RSS_THRESHOLD_MODIFIER:
                                 updatePssToRssThresholdModifier();
                                 break;
+                            case KEY_PROC_STATE_DEBUG_UIDS:
+                                updateProcStateDebugUids();
+                                break;
                             default:
                                 updateFGSPermissionEnforcementFlagsIfNecessary(name);
                                 break;
@@ -2039,6 +2055,76 @@
                 DEFAULT_MAX_PREVIOUS_TIME);
     }
 
+    private void updateProcStateDebugUids() {
+        final String val = DeviceConfig.getString(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_PROC_STATE_DEBUG_UIDS,
+                "").trim();
+
+        // Parse KEY_PROC_STATE_DEBUG_UIDS as comma-separated values. Each values can be:
+        // Number:  Enable debugging on the given UID.
+        // "stack": Enable stack trace when updating proc/uid-states.s
+        // "u" + delay-ms: Enable sleep when updating uid-state
+        // "p" + delay-ms: Enable sleep when updating procstate
+        //
+        // Example:
+        //   device_config put activity_manager proc_state_debug_uids '10177,10202,stack,p500,u100'
+        // means:
+        // - Monitor UID 10177 and 10202
+        // - Also enable stack trace
+        // - Sleep 500 ms when updating the procstate.
+        // - Sleep 100 ms when updating the UID state.
+
+        mEnableProcStateStacktrace = false;
+        mProcStateDebugSetProcStateDelay = 0;
+        mProcStateDebugSetUidStateDelay = 0;
+        if (val.length() == 0) {
+            mProcStateDebugUids = new SparseBooleanArray(0);
+            return;
+        }
+        final String[] uids = val.split(",");
+
+        final SparseBooleanArray newArray = new SparseBooleanArray(0);
+
+        for (String token : uids) {
+            if (token.length() == 0) {
+                continue;
+            }
+            // "stack" -> enable stacktrace.
+            if ("stack".equals(token)) {
+                mEnableProcStateStacktrace = true;
+                continue;
+            }
+            boolean isUid = true;
+            char prefix = token.charAt(0);
+            if ('a' <= prefix && prefix <= 'z') {
+                // If the token starts with an alphabet, it's not a UID.
+                isUid = false;
+                token = token.substring(1);
+            }
+
+            int value = -1;
+            try {
+                value = Integer.parseInt(token.trim());
+            } catch (NumberFormatException e) {
+                Slog.w(TAG, "Invalid number " + token + " in " + val);
+                continue;
+            }
+            if (isUid) {
+                newArray.put(value, true);
+            } else if (prefix == 'p') {
+                // Enable delay in set-proc-state
+                mProcStateDebugSetProcStateDelay = value;
+            } else if (prefix == 'u') {
+                // Enable delay in set-uid-state
+                mProcStateDebugSetUidStateDelay = value;
+            } else {
+                Slog.w(TAG, "Invalid prefix " + prefix + " in " + val);
+            }
+        }
+        mProcStateDebugUids = newArray;
+    }
+
     private void updateMinAssocLogDuration() {
         MIN_ASSOC_LOG_DURATION = DeviceConfig.getLong(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_MIN_ASSOC_LOG_DURATION,
@@ -2178,6 +2264,28 @@
                 mDefaultPssToRssThresholdModifier);
     }
 
+    boolean shouldDebugUidForProcState(int uid) {
+        SparseBooleanArray ar = mProcStateDebugUids;
+        final var size = ar.size();
+        if (size == 0) { // Most common case.
+            return false;
+        }
+        // If the array is small (also common), avoid the binary search.
+        if (size <= 8) {
+            for (int i = 0; i < size; i++) {
+                if (ar.keyAt(i) == uid) {
+                    return ar.valueAt(i);
+                }
+            }
+            return false;
+        }
+        return ar.get(uid, false);
+    }
+
+    boolean shouldEnableProcStateDebug() {
+        return mProcStateDebugUids.size() > 0;
+    }
+
     @NeverCompile // Avoid size overhead of debugging code.
     void dump(PrintWriter pw) {
         pw.println("ACTIVITY MANAGER SETTINGS (dumpsys activity settings) "
@@ -2393,5 +2501,12 @@
         pw.print("  OOMADJ_UPDATE_QUICK="); pw.println(OOMADJ_UPDATE_QUICK);
         pw.print("  ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION=");
         pw.println(mEnableWaitForFinishAttachApplication);
+
+        synchronized (mProcStateDebugUids) {
+            pw.print("  "); pw.print(KEY_PROC_STATE_DEBUG_UIDS);
+            pw.print("="); pw.println(mProcStateDebugUids);
+            pw.print("    uid-state-delay="); pw.println(mProcStateDebugSetUidStateDelay);
+            pw.print("    proc-state-delay="); pw.println(mProcStateDebugSetProcStateDelay);
+        }
     }
 }
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/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 0a6e9d3..5521381 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -286,6 +286,9 @@
     // when the flag is fused on.
     private static final int MSG_DELIVERY_TIMEOUT_SOFT = 8;
 
+    // TODO: Use the trunk stable flag.
+    private static final boolean DEFER_FROZEN_OUTGOING_BCASTS = false;
+
     private void enqueueUpdateRunningList() {
         mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);
         mLocalHandler.sendEmptyMessage(MSG_UPDATE_RUNNING_LIST);
@@ -763,7 +766,7 @@
         // TODO: Apply delivery group policies and FLAG_REPLACE_PENDING to collapse the
         // outgoing broadcasts.
         // TODO: Add traces/logs for the enqueueing outgoing broadcasts logic.
-        if (Flags.deferOutgoingBcasts() && isProcessFreezable(r.callerApp)) {
+        if (DEFER_FROZEN_OUTGOING_BCASTS && isProcessFreezable(r.callerApp)) {
             final BroadcastProcessQueue queue = getOrCreateProcessQueue(
                     r.callerApp.processName, r.callerApp.uid);
             if (queue.getOutgoingBroadcastCount() >= mConstants.MAX_FROZEN_OUTGOING_BROADCASTS) {
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index 8038732..b142781 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -135,3 +135,9 @@
 
 # Caller information to clear application data
 30120 am_clear_app_data_caller (pid|1),(uid|1),(package|3)
+
+30111 am_uid_state_changed (UID|1|5),(Seq|1|5),(UidState|1|5),(OldUidState|1|5),(Capability|1|5),(OldCapability|1|5),(Flags|1|5),(reason|3)
+30112 am_proc_state_changed (UID|1|5),(PID|1|5),(Seq|1|5),(ProcState|1|5),(OldProcState|1|5),(OomAdj|1|5),(OldOomAdj|1|5),(reason|3)
+
+# "Misc" events. See OomAdjusterDebugLogger.java
+30113 am_oom_adj_misc (Event|1|5),(UID|1|5),(PID|1|5),(Seq|1|5),(Arg1|1|5),(Arg2|1|5),(reason|3)
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 7f6d62c..1a7629f 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -401,6 +401,14 @@
     @GuardedBy("mService")
     private boolean mPendingFullOomAdjUpdate = false;
 
+    /**
+     * Most recent reason string. We update it in sync with the trace.
+     */
+    @OomAdjReason
+    protected int mLastReason;
+
+    private final OomAdjusterDebugLogger mLogger;
+
     /** Overrideable by a test */
     @VisibleForTesting
     protected boolean isChangeEnabled(@CachedCompatChangeId int cachedCompatChangeId,
@@ -433,6 +441,8 @@
         mCachedAppOptimizer = new CachedAppOptimizer(mService);
         mCacheOomRanker = new CacheOomRanker(service);
 
+        mLogger = new OomAdjusterDebugLogger(this, mService.mConstants);
+
         mProcessGroupHandler = new Handler(adjusterThread.getLooper(), msg -> {
             final int pid = msg.arg1;
             final int group = msg.arg2;
@@ -636,6 +646,7 @@
     protected boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) {
         final ProcessRecord topApp = mService.getTopApp();
 
+        mLastReason = oomAdjReason;
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
         mService.mOomAdjProfiler.oomAdjStarted();
         mAdjSeq++;
@@ -916,6 +927,7 @@
     protected void performUpdateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
         final ProcessRecord topApp = mService.getTopApp();
 
+        mLastReason = oomAdjReason;
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
         mService.mOomAdjProfiler.oomAdjStarted();
 
@@ -958,6 +970,7 @@
             }
         }
 
+        mLastReason = oomAdjReason;
         if (startProfiling) {
             Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
             mService.mOomAdjProfiler.oomAdjStarted();
@@ -1491,6 +1504,7 @@
                         || uidRec.isSetAllowListed() != uidRec.isCurAllowListed()
                         || uidRec.getProcAdjChanged()) {
                     int uidChange = 0;
+                    final boolean shouldLog = mLogger.shouldLog(uidRec.getUid());
                     if (DEBUG_UID_OBSERVERS) {
                         Slog.i(TAG_UID_OBSERVERS, "Changes in " + uidRec
                                 + ": proc state from " + uidRec.getSetProcState() + " to "
@@ -1511,14 +1525,21 @@
                                 || uidRec.isSetAllowListed()
                                 || uidRec.getLastBackgroundTime() == 0) {
                             uidRec.setLastBackgroundTime(nowElapsed);
+                            if (shouldLog) {
+                                mLogger.logSetLastBackgroundTime(uidRec.getUid(), nowElapsed);
+                            }
                             if (mService.mDeterministicUidIdle
                                     || !mService.mHandler.hasMessages(IDLE_UIDS_MSG)) {
                                 // Note: the background settle time is in elapsed realtime, while
                                 // the handler time base is uptime.  All this means is that we may
                                 // stop background uids later than we had intended, but that only
                                 // happens because the device was sleeping so we are okay anyway.
+                                if (shouldLog) {
+                                    mLogger.logScheduleUidIdle1(uidRec.getUid(),
+                                            mConstants.BACKGROUND_SETTLE_TIME);
+                                }
                                 mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,
-                                        mConstants.BACKGROUND_SETTLE_TIME);
+                                        mConstants.BACKGROUND_SETTLE_TIME); // XXX
                             }
                         }
                         if (uidRec.isIdle() && !uidRec.isSetIdle()) {
@@ -1536,6 +1557,9 @@
                         }
                         uidRec.setLastBackgroundTime(0);
                         uidRec.setLastIdleTime(0);
+                        if (shouldLog) {
+                            mLogger.logClearLastBackgroundTime(uidRec.getUid());
+                        }
                     }
                     final boolean wasCached = uidRec.getSetProcState()
                             > ActivityManager.PROCESS_STATE_RECEIVER;
@@ -1555,11 +1579,25 @@
                     if (uidRec.getProcAdjChanged()) {
                         uidChange |= UidRecord.CHANGE_PROCADJ;
                     }
+                    int oldProcState = uidRec.getSetProcState();
+                    int oldCapability = uidRec.getSetCapability();
                     uidRec.setSetProcState(uidRec.getCurProcState());
                     uidRec.setSetCapability(uidRec.getCurCapability());
                     uidRec.setSetAllowListed(uidRec.isCurAllowListed());
                     uidRec.setSetIdle(uidRec.isIdle());
                     uidRec.clearProcAdjChanged();
+                    if (shouldLog
+                            && ((uidRec.getSetProcState() != oldProcState)
+                            || (uidRec.getSetCapability() != oldCapability))) {
+                        int flags = 0;
+                        if (uidRec.isSetAllowListed()) {
+                            flags |= 1;
+                        }
+                        mLogger.logUidStateChanged(uidRec.getUid(),
+                                uidRec.getSetProcState(), oldProcState,
+                                uidRec.getSetCapability(), oldCapability,
+                                flags);
+                    }
                     if ((uidChange & UidRecord.CHANGE_PROCSTATE) != 0
                             || (uidChange & UidRecord.CHANGE_CAPABILITY) != 0) {
                         mService.mAtmInternal.onUidProcStateChanged(
@@ -3300,6 +3338,7 @@
             mCachedAppOptimizer.onOomAdjustChanged(state.getSetAdj(), state.getCurAdj(), app);
         }
 
+        final int oldOomAdj = state.getSetAdj();
         if (state.getCurAdj() != state.getSetAdj()) {
             ProcessList.setOomAdj(app.getPid(), app.uid, state.getCurAdj());
             if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.info.uid) {
@@ -3457,6 +3496,7 @@
             mService.mAppProfiler.updateNextPssTimeLPf(
                     state.getCurProcState(), app.mProfile, now, forceUpdatePssTime);
         }
+        int oldProcState = state.getSetProcState();
         if (state.getSetProcState() != state.getCurProcState()) {
             if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.uid) {
                 String msg = "Proc state change of " + app.processName
@@ -3556,6 +3596,11 @@
                 // Kick off the delayed checkup message if needed.
                 if (mService.mDeterministicUidIdle
                         || !mService.mHandler.hasMessages(IDLE_UIDS_MSG)) {
+                    if (mLogger.shouldLog(app.uid)) {
+                        mLogger.logScheduleUidIdle2(
+                                uidRec.getUid(), app.getPid(),
+                                mConstants.mKillBgRestrictedAndCachedIdleSettleTimeMs);
+                    }
                     mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,
                             mConstants.mKillBgRestrictedAndCachedIdleSettleTimeMs);
                 }
@@ -3563,6 +3608,12 @@
         }
         state.setSetCached(state.isCached());
         state.setSetNoKillOnBgRestrictedAndIdle(state.shouldNotKillOnBgRestrictedAndIdle());
+        if (((oldProcState != state.getSetProcState()) || (oldOomAdj != state.getSetAdj()))
+                && mLogger.shouldLog(app.uid)) {
+            mLogger.logProcStateChanged(app.uid, app.getPid(),
+                    state.getSetProcState(), oldProcState,
+                    state.getSetAdj(), oldOomAdj);
+        }
 
         return success;
     }
@@ -3704,6 +3755,7 @@
         if (mService.mLocalPowerManager != null) {
             mService.mLocalPowerManager.startUidChanges();
         }
+        boolean shouldLogMisc = false;
         for (int i = N - 1; i >= 0; i--) {
             final UidRecord uidRec = mActiveUids.valueAt(i);
             final long bgTime = uidRec.getLastBackgroundTime();
@@ -3721,6 +3773,9 @@
                     if (nextTime == 0 || nextTime > bgTime) {
                         nextTime = bgTime;
                     }
+                    if (mLogger.shouldLog(uidRec.getUid())) {
+                        shouldLogMisc = true;
+                    }
                 }
             }
         }
@@ -3742,8 +3797,11 @@
             }
         }
         if (nextTime > 0) {
-            mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,
-                    nextTime + mConstants.BACKGROUND_SETTLE_TIME - nowElapsed);
+            long delay = nextTime + mConstants.BACKGROUND_SETTLE_TIME - nowElapsed;
+            if (shouldLogMisc) {
+                mLogger.logScheduleUidIdle3(delay);
+            }
+            mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG, delay);
         }
     }
 
diff --git a/services/core/java/com/android/server/am/OomAdjusterDebugLogger.java b/services/core/java/com/android/server/am/OomAdjusterDebugLogger.java
new file mode 100644
index 0000000..1294a4d
--- /dev/null
+++ b/services/core/java/com/android/server/am/OomAdjusterDebugLogger.java
@@ -0,0 +1,110 @@
+/*
+ * 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.am;
+
+import android.app.StackTrace;
+import android.util.Slog;
+
+/**
+ * Helper for writing debug log about proc/uid state changes.
+ */
+class OomAdjusterDebugLogger {
+    // Use the "am_" tag to make it similar to  event logs.
+    private static final String STACK_TRACE_TAG = "am_stack";
+
+    private final OomAdjuster mOomAdjuster;
+    private final ActivityManagerConstants mConstants;
+
+    private static final int MISC_SCHEDULE_IDLE_UIDS_MSG_1 = 1;
+    private static final int MISC_SCHEDULE_IDLE_UIDS_MSG_2 = 2;
+    private static final int MISC_SCHEDULE_IDLE_UIDS_MSG_3 = 3;
+
+    private static final int MISC_SET_LAST_BG_TIME = 10;
+    private static final int MISC_CLEAR_LAST_BG_TIME = 11;
+
+    OomAdjusterDebugLogger(OomAdjuster oomAdjuster, ActivityManagerConstants constants) {
+        mOomAdjuster = oomAdjuster;
+        mConstants = constants;
+    }
+
+    boolean shouldLog(int uid) {
+        return mConstants.shouldDebugUidForProcState(uid);
+    }
+
+    private void maybeLogStacktrace(String msg) {
+        if (!mConstants.mEnableProcStateStacktrace) {
+            return;
+        }
+        Slog.i(STACK_TRACE_TAG,
+                msg + ": " + OomAdjuster.oomAdjReasonToString(mOomAdjuster.mLastReason),
+                new StackTrace("Called here"));
+    }
+
+    private void maybeSleep(int millis) {
+        if (millis == 0) {
+            return;
+        }
+        try {
+            Thread.sleep(millis);
+        } catch (InterruptedException e) {
+        }
+    }
+
+    void logUidStateChanged(int uid, int uidstate, int olduidstate,
+            int capability, int oldcapability, int flags) {
+        EventLogTags.writeAmUidStateChanged(
+                uid, mOomAdjuster.mAdjSeq, uidstate, olduidstate, capability, oldcapability, flags,
+                OomAdjuster.oomAdjReasonToString(mOomAdjuster.mLastReason));
+        maybeLogStacktrace("uidStateChanged");
+        maybeSleep(mConstants.mProcStateDebugSetUidStateDelay);
+    }
+
+    void logProcStateChanged(int uid, int pid, int procstate, int oldprocstate,
+            int oomadj, int oldoomadj) {
+        EventLogTags.writeAmProcStateChanged(
+                uid, pid, mOomAdjuster.mAdjSeq, procstate, oldprocstate, oomadj, oldoomadj,
+                OomAdjuster.oomAdjReasonToString(mOomAdjuster.mLastReason));
+        maybeLogStacktrace("procStateChanged");
+        maybeSleep(mConstants.mProcStateDebugSetProcStateDelay);
+    }
+
+    void logScheduleUidIdle1(int uid, long delay) {
+        EventLogTags.writeAmOomAdjMisc(MISC_SCHEDULE_IDLE_UIDS_MSG_1,
+                uid, 0, mOomAdjuster.mAdjSeq, (int) delay, 0, "");
+    }
+
+    void logScheduleUidIdle2(int uid, int pid, long delay) {
+        EventLogTags.writeAmOomAdjMisc(MISC_SCHEDULE_IDLE_UIDS_MSG_2,
+                uid, pid, mOomAdjuster.mAdjSeq, (int) delay, 0, "");
+    }
+
+    void logScheduleUidIdle3(long delay) {
+        EventLogTags.writeAmOomAdjMisc(MISC_SCHEDULE_IDLE_UIDS_MSG_3,
+                0, 0, mOomAdjuster.mAdjSeq, (int) delay, 0, "");
+    }
+
+    void logSetLastBackgroundTime(int uid, long time) {
+        EventLogTags.writeAmOomAdjMisc(MISC_SET_LAST_BG_TIME,
+                uid, 0, mOomAdjuster.mAdjSeq, (int) time, 0,
+                OomAdjuster.oomAdjReasonToString(mOomAdjuster.mLastReason));
+    }
+
+    void logClearLastBackgroundTime(int uid) {
+        EventLogTags.writeAmOomAdjMisc(MISC_CLEAR_LAST_BG_TIME,
+                uid, 0, mOomAdjuster.mAdjSeq, 0, 0,
+                OomAdjuster.oomAdjReasonToString(mOomAdjuster.mLastReason));
+    }
+}
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index 46bdfe8..5feac1f 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -741,6 +741,7 @@
         mPendingProcessSet.clear();
         mService.mAppProfiler.mHasPreviousProcess = mService.mAppProfiler.mHasHomeProcess = false;
 
+        mLastReason = oomAdjReason;
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
         mService.mOomAdjProfiler.oomAdjStarted();
 
@@ -761,6 +762,7 @@
     @GuardedBy("mService")
     @Override
     protected void performUpdateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
+        mLastReason = oomAdjReason;
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
         mService.mOomAdjProfiler.oomAdjStarted();
 
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 e915688..b7e11a7 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -20,7 +20,7 @@
 // TODO(b/141025588): Create separate internal and external permissions for AuthService.
 // TODO(b/141025588): Get rid of the USE_FINGERPRINT permission.
 
-import static android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO;
+import static android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED;
 import static android.Manifest.permission.TEST_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
@@ -1030,8 +1030,8 @@
     }
 
     private void checkManageBiometricPermission() {
-        getContext().enforceCallingOrSelfPermission(SET_BIOMETRIC_DIALOG_LOGO,
-                "Must have SET_BIOMETRIC_DIALOG_LOGO permission");
+        getContext().enforceCallingOrSelfPermission(SET_BIOMETRIC_DIALOG_ADVANCED,
+                "Must have SET_BIOMETRIC_DIALOG_ADVANCED permission");
     }
 
     private void checkPermission() {
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/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/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/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/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index a100fe0..29cccdf 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -74,7 +74,6 @@
     @GuardedBy("ImfLock.class") @Nullable private IInputMethodInvoker mCurMethod;
     @GuardedBy("ImfLock.class") private int mCurMethodUid = Process.INVALID_UID;
     @GuardedBy("ImfLock.class") @Nullable private IBinder mCurToken;
-    @GuardedBy("ImfLock.class") private int mCurSeq;
     @GuardedBy("ImfLock.class") private boolean mVisibleBound;
     @GuardedBy("ImfLock.class") private boolean mSupportsStylusHw;
     @GuardedBy("ImfLock.class") private boolean mSupportsConnectionlessStylusHw;
@@ -195,27 +194,6 @@
     }
 
     /**
-     * The current binding sequence number, incremented every time there is
-     * a new bind performed.
-     */
-    @GuardedBy("ImfLock.class")
-    int getSequenceNumber() {
-        return mCurSeq;
-    }
-
-    /**
-     * Increase the current binding sequence number by one.
-     * Reset to 1 on overflow.
-     */
-    @GuardedBy("ImfLock.class")
-    void advanceSequenceNumber() {
-        mCurSeq += 1;
-        if (mCurSeq <= 0) {
-            mCurSeq = 1;
-        }
-    }
-
-    /**
      * If non-null, this is the input method service we are currently connected
      * to.
      */
@@ -435,9 +413,11 @@
             mLastBindTime = SystemClock.uptimeMillis();
 
             addFreshWindowToken();
+            final UserData monitor = UserData.getOrCreate(
+                    mService.getCurrentImeUserIdLocked());
             return new InputBindResult(
                     InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
-                    null, null, null, mCurId, mCurSeq, false);
+                    null, null, null, mCurId, monitor.mSequence.getSequenceNumber(), false);
         }
 
         Slog.w(InputMethodManagerService.TAG,
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index a0e910d..d0a83a6 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -285,12 +285,11 @@
     final Resources mRes;
     private final Handler mHandler;
 
-    /**
-     * TODO(b/329163064): Remove this field.
-     */
-    @NonNull
     @MultiUserUnawareField
-    private InputMethodSettings mSettings;
+    @UserIdInt
+    @GuardedBy("ImfLock.class")
+    private int mCurrentUserId;
+
     @MultiUserUnawareField
     final SettingsObserver mSettingsObserver;
     final WindowManagerInternal mWindowManagerInternal;
@@ -480,7 +479,8 @@
      */
     @GuardedBy("ImfLock.class")
     private int getSequenceNumberLocked() {
-        return mBindingController.getSequenceNumber();
+        final UserData monitor = UserData.getOrCreate(mCurrentUserId);
+        return monitor.mSequence.getSequenceNumber();
     }
 
     /**
@@ -489,13 +489,14 @@
      */
     @GuardedBy("ImfLock.class")
     private void advanceSequenceNumberLocked() {
-        mBindingController.advanceSequenceNumber();
+        final UserData monitor = UserData.getOrCreate(mCurrentUserId);
+        monitor.mSequence.advanceSequenceNumber();
     }
 
     @GuardedBy("ImfLock.class")
     @Nullable
     InputMethodInfo queryInputMethodForCurrentUserLocked(@NonNull String imeId) {
-        return mSettings.getMethodMap().get(imeId);
+        return InputMethodSettingsRepository.get(mCurrentUserId).getMethodMap().get(imeId);
     }
 
     /**
@@ -559,10 +560,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
@@ -813,7 +817,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;
@@ -845,9 +850,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();
@@ -878,9 +885,6 @@
                                 AdditionalSubtypeMapRepository.get(userId),
                                 DirectBootAwareness.AUTO);
                 InputMethodSettingsRepository.put(userId, settings);
-                if (userId == mSettings.getUserId()) {
-                    mSettings = settings;
-                }
             }
             postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */);
             // If the locale is changed, needs to reset the default ime
@@ -942,7 +946,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);
@@ -957,8 +961,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++) {
@@ -1075,7 +1081,7 @@
         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 = InputMethodSettingsRepository.get(userId);
@@ -1133,8 +1139,6 @@
                 if (!isCurrentUser) {
                     return;
                 }
-                mSettings = queryInputMethodServicesInternal(mContext, userId,
-                        newAdditionalSubtypeMap, DirectBootAwareness.AUTO);
                 postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
 
                 boolean changed = false;
@@ -1288,8 +1292,7 @@
     void onUnlockUser(@UserIdInt int userId) {
         synchronized (ImfLock.class) {
             if (DEBUG) {
-                Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId="
-                        + mSettings.getUserId());
+                Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + mCurrentUserId);
             }
             if (!mSystemReady) {
                 return;
@@ -1297,8 +1300,7 @@
             final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
                     userId, AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO);
             InputMethodSettingsRepository.put(userId, newSettings);
-            if (mSettings.getUserId() == userId) {
-                mSettings = newSettings;
+            if (mCurrentUserId == userId) {
                 // We need to rebuild IMEs.
                 postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
                 updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
@@ -1369,17 +1371,18 @@
         // InputMethodSettingsRepository should be initialized before buildInputMethodListLocked
         InputMethodSettingsRepository.initialize(mHandler, mContext);
         AdditionalSubtypeMapRepository.initialize(mHandler, mContext);
+        UserData.initialize(mHandler);
 
-        final int userId = mActivityManagerInternal.getCurrentUserId();
+        mCurrentUserId = mActivityManagerInternal.getCurrentUserId();
 
-        mSettings = InputMethodSettingsRepository.get(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
@@ -1412,7 +1415,7 @@
     @GuardedBy("ImfLock.class")
     @UserIdInt
     int getCurrentImeUserIdLocked() {
-        return mSettings.getUserId();
+        return mCurrentUserId;
     }
 
     private final class InkWindowInitializer implements Runnable {
@@ -1448,12 +1451,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;
@@ -1509,7 +1513,7 @@
             IInputMethodClientInvoker clientToBeReset) {
         if (DEBUG) {
             Slog.d(TAG, "Switching user stage 1/3. newUserId=" + newUserId
-                    + " currentUserId=" + mSettings.getUserId());
+                    + " currentUserId=" + mCurrentUserId);
         }
 
         maybeInitImeNavbarConfigLocked(newUserId);
@@ -1517,8 +1521,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
@@ -1536,12 +1541,9 @@
         // and user switch would not happen at that time.
         resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_USER);
 
-        final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
-                newUserId, AdditionalSubtypeMapRepository.get(newUserId), DirectBootAwareness.AUTO);
-        InputMethodSettingsRepository.put(newUserId, newSettings);
-        mSettings = newSettings;
+        final InputMethodSettings newSettings = InputMethodSettingsRepository.get(newUserId);
         postInputMethodSettingUpdatedLocked(initialUserSwitch /* resetDefaultEnabledIme */);
-        if (TextUtils.isEmpty(mSettings.getSelectedInputMethod())) {
+        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);
@@ -1551,12 +1553,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) {
@@ -1579,7 +1581,7 @@
             }
             if (!mSystemReady) {
                 mSystemReady = true;
-                final int currentUserId = mSettings.getUserId();
+                final int currentUserId = mCurrentUserId;
                 mStatusBarManagerInternal =
                         LocalServices.getService(StatusBarManagerInternal.class);
                 hideStatusBarIconLocked();
@@ -1600,7 +1602,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.
@@ -1619,19 +1621,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);
                 final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
                         currentUserId, AdditionalSubtypeMapRepository.get(currentUserId),
                         DirectBootAwareness.AUTO);
                 InputMethodSettingsRepository.put(currentUserId, newSettings);
-                mSettings = newSettings;
                 postInputMethodSettingUpdatedLocked(
                         !imeSelectedOnBoot /* resetDefaultEnabledIme */);
                 updateFromSettingsLocked(true);
                 InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
                         getPackageManagerForUser(mContext, currentUserId),
-                        mSettings.getEnabledInputMethodList());
+                        newSettings.getEnabledInputMethodList());
             }
         }
     }
@@ -1678,7 +1680,7 @@
         }
         synchronized (ImfLock.class) {
             final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId,
-                    mSettings.getUserId(), null);
+                    mCurrentUserId, null);
             if (resolvedUserIds.length != 1) {
                 return Collections.emptyList();
             }
@@ -1701,7 +1703,7 @@
         }
         synchronized (ImfLock.class) {
             final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId,
-                    mSettings.getUserId(), null);
+                    mCurrentUserId, null);
             if (resolvedUserIds.length != 1) {
                 return Collections.emptyList();
             }
@@ -1729,7 +1731,7 @@
             }
 
             // Check if selected IME of current user supports handwriting.
-            if (userId == mSettings.getUserId()) {
+            if (userId == mCurrentUserId) {
                 return mBindingController.supportsStylusHandwriting()
                         && (!connectionless
                                 || mBindingController.supportsConnectionlessStylusHandwriting());
@@ -1777,15 +1779,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 = InputMethodSettingsRepository.get(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));
@@ -2023,7 +2018,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),
@@ -2037,9 +2032,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 */);
         }
@@ -2062,7 +2057,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 =
@@ -2242,17 +2238,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;
         }
 
@@ -2260,7 +2257,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);
@@ -2272,7 +2269,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
@@ -2302,7 +2299,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;
         }
@@ -2646,7 +2644,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,
@@ -2708,7 +2706,7 @@
             return false;
         }
         if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded()
-                && mWindowManagerInternal.isKeyguardSecure(mSettings.getUserId())) {
+                && mWindowManagerInternal.isKeyguardSecure(mCurrentUserId)) {
             return false;
         }
         if ((visibility & InputMethodService.IME_ACTIVE) == 0
@@ -2725,7 +2723,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;
@@ -2737,7 +2736,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;
@@ -2848,12 +2847,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;
@@ -2885,11 +2888,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.
@@ -2916,20 +2920,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());
             }
         }
 
@@ -2937,14 +2941,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);
@@ -2955,18 +2959,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();
     }
@@ -2990,14 +2994,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);
@@ -3038,7 +3043,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();
@@ -3318,11 +3323,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();
             }
         });
@@ -3548,7 +3554,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);
@@ -3594,10 +3600,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);
@@ -3700,7 +3706,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
@@ -3786,7 +3792,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(
@@ -3854,9 +3860,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);
@@ -3872,9 +3879,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) {
@@ -3892,10 +3900,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;
             }
@@ -3919,7 +3928,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;
@@ -3927,7 +3936,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);
@@ -3974,8 +3983,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;
@@ -3991,9 +4001,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;
         }
     }
@@ -4005,10 +4016,6 @@
                     Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
         }
         synchronized (ImfLock.class) {
-            if (mSettings.getUserId() == userId) {
-                return mSettings.getLastInputMethodSubtype();
-            }
-
             return InputMethodSettingsRepository.get(userId).getLastInputMethodSubtype();
         }
     }
@@ -4040,7 +4047,7 @@
             }
 
             final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
-            final boolean isCurrentUser = (mSettings.getUserId() == userId);
+            final boolean isCurrentUser = (mCurrentUserId == userId);
             final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
             final var newAdditionalSubtypeMap = settings.getNewAdditionalSubtypeMap(
                     imiId, toBeAdded, additionalSubtypeMap, mPackageManagerInternal, callingUid);
@@ -4054,7 +4061,6 @@
                 if (isCurrentUser) {
                     final long ident = Binder.clearCallingIdentity();
                     try {
-                        mSettings = newSettings;
                         postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
                     } finally {
                         Binder.restoreCallingIdentity(ident);
@@ -4084,7 +4090,7 @@
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (ImfLock.class) {
-                final boolean currentUser = (mSettings.getUserId() == userId);
+                final boolean currentUser = (mCurrentUserId == userId);
                 final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
                 if (!settings.setEnabledInputMethodSubtypes(imeId, subtypeHashCodes)) {
                     return;
@@ -4435,11 +4441,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);
             }
@@ -4499,8 +4505,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);
             }
@@ -4679,21 +4686,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;
                     }
 
@@ -4879,8 +4888,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());
@@ -4994,6 +5004,8 @@
         mMethodMapUpdateCount++;
         mMyPackageMonitor.clearKnownImePackageNamesLocked();
 
+        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
         // changed.
@@ -5004,7 +5016,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;
@@ -5019,11 +5031,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;
@@ -5047,7 +5059,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) {
@@ -5059,9 +5071,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);
@@ -5075,26 +5087,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")
@@ -5109,11 +5121,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,"
@@ -5122,7 +5135,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.
             }
@@ -5135,7 +5148,7 @@
             Slog.i(TAG, "Enabling the default Voice IME:" + newSystemVoiceIme);
         }
         setInputMethodEnabledLocked(newSystemVoiceIme.getId(), true);
-        mSettings.putDefaultVoiceInputMethod(newSystemVoiceIme.getId());
+        settings.putDefaultVoiceInputMethod(newSystemVoiceIme.getId());
     }
 
     // ----------------------------------------------------------------------
@@ -5150,8 +5163,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)) {
@@ -5159,29 +5173,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.
@@ -5197,29 +5211,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() : "");
         }
     }
 
@@ -5227,13 +5242,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,
@@ -5260,7 +5277,7 @@
                     Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
         }
         synchronized (ImfLock.class) {
-            if (mSettings.getUserId() == userId) {
+            if (mCurrentUserId == userId) {
                 return getCurrentInputMethodSubtypeLocked();
             }
 
@@ -5285,26 +5302,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,
@@ -5333,16 +5351,16 @@
 
     @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 = InputMethodSettingsRepository.get(userId);
         if (!settings.getMethodMap().containsKey(imeId)
                 || !settings.getEnabledInputMethodList().contains(
                         settings.getMethodMap().get(imeId))) {
@@ -5383,8 +5401,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;
         }
@@ -5396,7 +5415,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;
         }
@@ -5475,17 +5494,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 = 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(
@@ -5541,9 +5557,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);
                 }
             }
         }
@@ -5818,8 +5836,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++) {
@@ -5843,11 +5862,11 @@
                 p.println("    curSession=" + c.mCurSession);
             };
             mClientController.forAllClients(clientControllerDump);
-
+            p.println("  mCurrentUserId=" + mCurrentUserId);
             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="
@@ -5869,8 +5888,6 @@
                     ? Arrays.toString(mStylusIds.toArray()) : ""));
             p.println("  mSwitchingController:");
             mSwitchingController.dump(p);
-            p.println("  mSettings:");
-            mSettings.dump(p, "    ");
 
             p.println("  mStartInputHistory:");
             mStartInputHistory.dump(pw, "    ");
@@ -6125,7 +6142,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
@@ -6170,7 +6187,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;
@@ -6229,14 +6246,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 = InputMethodSettingsRepository.get(userId);
             if (enabled) {
                 if (!settings.getMethodMap().containsKey(imeId)) {
                     failedToEnableUnknownIme = true;
@@ -6291,7 +6308,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;
@@ -6331,7 +6348,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;
@@ -6343,15 +6360,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);
@@ -6365,14 +6383,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 InputMethodSettings settings =
-                                InputMethodSettingsRepository.get(userId);
-
                         nextEnabledImes = InputMethodInfoUtils.getDefaultEnabledImes(mContext,
                                 settings.getMethodList());
                         nextIme = InputMethodInfoUtils.getMostApplicableDefaultIME(
diff --git a/services/core/java/com/android/server/inputmethod/Sequence.java b/services/core/java/com/android/server/inputmethod/Sequence.java
new file mode 100644
index 0000000..05e31ce
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/Sequence.java
@@ -0,0 +1,45 @@
+/*
+ * 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 com.android.internal.annotations.GuardedBy;
+
+/**
+ * A sequence number utility class that only generate positive numbers.
+ */
+final class Sequence {
+
+    private final Object mLock = new Object();
+
+    private int mSequence;
+
+    int getSequenceNumber() {
+        synchronized (mLock) {
+            return mSequence;
+        }
+    }
+
+    @GuardedBy("ImfLock.class")
+    void advanceSequenceNumber() {
+        synchronized (mLock) {
+            mSequence++;
+            if (mSequence <= 0) {
+                mSequence = 1;
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/UserData.java b/services/core/java/com/android/server/inputmethod/UserData.java
new file mode 100644
index 0000000..fc2a422
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/UserData.java
@@ -0,0 +1,88 @@
+/*
+ * 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.pm.UserInfo;
+import android.os.Handler;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+final class UserData {
+
+    @NonNull
+    private static final SparseArray<UserData> sPerUserMonitor = new SparseArray<>();
+
+    @UserIdInt
+    final int mUserId;
+
+    @GuardedBy("ImfLock.class")
+    final Sequence mSequence = new Sequence();
+
+    /**
+     * Not intended to be instantiated.
+     */
+    private UserData(int userId) {
+        mUserId = userId;
+    }
+
+    @GuardedBy("ImfLock.class")
+    static UserData getOrCreate(@UserIdInt int userId) {
+        UserData monitor = sPerUserMonitor.get(userId);
+        if (monitor == null) {
+            monitor = new UserData(userId);
+            sPerUserMonitor.put(userId, monitor);
+        }
+        return monitor;
+    }
+
+    static void initialize(Handler handler) {
+        final UserManagerInternal userManagerInternal =
+                LocalServices.getService(UserManagerInternal.class);
+        userManagerInternal.addUserLifecycleListener(
+                new UserManagerInternal.UserLifecycleListener() {
+                    @Override
+                    public void onUserRemoved(UserInfo user) {
+                        final int userId = user.id;
+                        handler.post(() -> {
+                            synchronized (ImfLock.class) {
+                                sPerUserMonitor.remove(userId);
+                            }
+                        });
+                    }
+
+                    @Override
+                    public void onUserCreated(UserInfo user, Object unusedToken) {
+                        final int userId = user.id;
+                        handler.post(() -> {
+                            synchronized (ImfLock.class) {
+                                getOrCreate(userId);
+                            }
+                        });
+                    }
+                });
+        synchronized (ImfLock.class) {
+            for (int userId : userManagerInternal.getUserIds()) {
+                getOrCreate(userId);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 97ce77c..18b495b 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -340,8 +340,6 @@
     static final String TAG = NetworkPolicyLogger.TAG;
     private static final boolean LOGD = NetworkPolicyLogger.LOGD;
     private static final boolean LOGV = NetworkPolicyLogger.LOGV;
-    // TODO: b/304347838 - Remove once the feature is in staging.
-    private static final boolean ALWAYS_RESTRICT_BACKGROUND_NETWORK = false;
 
     /**
      * No opportunistic quota could be calculated from user data plan or data settings.
@@ -1070,8 +1068,7 @@
                     }
 
                     // The flag is boot-stable.
-                    mBackgroundNetworkRestricted = ALWAYS_RESTRICT_BACKGROUND_NETWORK
-                            && Flags.networkBlockedForTopSleepingAndAbove();
+                    mBackgroundNetworkRestricted = Flags.networkBlockedForTopSleepingAndAbove();
                     if (mBackgroundNetworkRestricted) {
                         // Firewall rules and UidBlockedState will get updated in
                         // updateRulesForGlobalChangeAL below.
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c1a4571e..e80c79a8 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -741,7 +741,7 @@
 
     private static final int MY_UID = Process.myUid();
     private static final int MY_PID = Process.myPid();
-    static final IBinder ALLOWLIST_TOKEN = new Binder();
+    private static final IBinder ALLOWLIST_TOKEN = new Binder();
     protected RankingHandler mRankingHandler;
     private long mLastOverRateLogTime;
     private float mMaxPackageEnqueueRate = DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE;
@@ -4875,7 +4875,7 @@
                     // Remove background token before returning notification to untrusted app, this
                     // ensures the app isn't able to perform background operations that are
                     // associated with notification interactions.
-                    notification.overrideAllowlistToken(null);
+                    notification.clearAllowlistToken();
                     return new StatusBarNotification(
                             sbn.getPackageName(),
                             sbn.getOpPkg(),
@@ -7884,8 +7884,6 @@
             }
         }
 
-        notification.overrideAllowlistToken(ALLOWLIST_TOKEN);
-
         // Remote views? Are they too big?
         checkRemoteViews(pkg, tag, id, notification);
     }
diff --git a/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/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index c9fd261..1919137 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -217,8 +217,7 @@
     private static final Set<String> IMMUTABLE_BY_OWNERS = Sets.newArraySet(
             UserManager.DISALLOW_RECORD_AUDIO,
             UserManager.DISALLOW_WALLPAPER,
-            UserManager.DISALLOW_OEM_UNLOCK,
-            UserManager.DISALLOW_ADD_PRIVATE_PROFILE
+            UserManager.DISALLOW_OEM_UNLOCK
     );
 
     /**
@@ -293,7 +292,8 @@
                     UserManager.DISALLOW_USB_FILE_TRANSFER,
                     UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA,
                     UserManager.DISALLOW_UNMUTE_MICROPHONE,
-                    UserManager.DISALLOW_CONFIG_DEFAULT_APPS
+                    UserManager.DISALLOW_CONFIG_DEFAULT_APPS,
+                    UserManager.DISALLOW_ADD_PRIVATE_PROFILE
     );
 
     /**
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 266418f..9e31748 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1506,9 +1506,7 @@
     }
 
     private void stemPrimaryPress(int count) {
-        if (DEBUG_INPUT) {
-            Slog.d(TAG, "stemPrimaryPress: " + count);
-        }
+        Slog.d(TAG, "stemPrimaryPress: " + count);
         if (count == 3) {
             stemPrimaryTriplePressAction(mTriplePressOnStemPrimaryBehavior);
         } else if (count == 2) {
@@ -1519,22 +1517,18 @@
     }
 
     private void stemPrimarySinglePressAction(int behavior) {
-        if (DEBUG_INPUT) {
-            Slog.d(TAG, "stemPrimarySinglePressAction: behavior=" + behavior);
-        }
+        Slog.d(TAG, "stemPrimarySinglePressAction: behavior=" + behavior);
         if (behavior == SHORT_PRESS_PRIMARY_NOTHING) return;
 
         final boolean keyguardActive = mKeyguardDelegate != null && mKeyguardDelegate.isShowing();
         if (keyguardActive) {
             // If keyguarded then notify the keyguard.
             mKeyguardDelegate.onSystemKeyPressed(KeyEvent.KEYCODE_STEM_PRIMARY);
+            Slog.d(TAG, "stemPrimarySinglePressAction: skip due to keyguard");
             return;
         }
         switch (behavior) {
             case SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS:
-                if (DEBUG_INPUT) {
-                    Slog.d(TAG, "Executing stem primary short press action behavior.");
-                }
                 Intent allAppsIntent = new Intent(Intent.ACTION_ALL_APPS);
                 allAppsIntent.addFlags(
                         Intent.FLAG_ACTIVITY_NEW_TASK
@@ -1542,12 +1536,6 @@
                 startActivityAsUser(allAppsIntent, UserHandle.CURRENT_OR_SELF);
                 break;
             case SHORT_PRESS_PRIMARY_LAUNCH_TARGET_ACTIVITY:
-                if (DEBUG_INPUT) {
-                    Slog.d(
-                            TAG,
-                            "Executing stem primary short press action behavior for launching "
-                                    + "target activity.");
-                }
                 if (mPrimaryShortPressTargetActivity != null) {
                     Intent targetActivityIntent = new Intent();
                     targetActivityIntent.setComponent(mPrimaryShortPressTargetActivity);
@@ -1578,13 +1566,11 @@
     }
 
     private void stemPrimaryDoublePressAction(int behavior) {
+        Slog.d(TAG, "stemPrimaryDoublePressAction: " + behavior);
         switch (behavior) {
             case DOUBLE_PRESS_PRIMARY_NOTHING:
                 break;
             case DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP:
-                if (DEBUG_INPUT) {
-                    Slog.d(TAG, "Executing stem primary double press action behavior.");
-                }
                 final boolean keyguardActive = mKeyguardDelegate == null
                         ? false
                         : mKeyguardDelegate.isShowing();
@@ -1596,13 +1582,11 @@
     }
 
     private void stemPrimaryTriplePressAction(int behavior) {
+        Slog.d(TAG, "stemPrimaryTriplePressAction: " + behavior);
         switch (behavior) {
             case TRIPLE_PRESS_PRIMARY_NOTHING:
                 break;
             case TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY:
-                if (DEBUG_INPUT) {
-                    Slog.d(TAG, "Executing stem primary triple press action behavior.");
-                }
                 mTalkbackShortcutController.toggleTalkback(mCurrentUserId);
                 if (mTalkbackShortcutController.isTalkBackShortcutGestureEnabled()) {
                     performHapticFeedback(HapticFeedbackConstants.CONFIRM, /* always = */
@@ -1614,9 +1598,7 @@
     }
 
     private void stemPrimaryLongPress(long eventTime) {
-        if (DEBUG_INPUT) {
-            Slog.d(TAG, "Executing stem primary long press action behavior.");
-        }
+        Slog.d(TAG, "stemPrimaryLongPress: "  + mLongPressOnStemPrimaryBehavior);
 
         switch (mLongPressOnStemPrimaryBehavior) {
             case LONG_PRESS_PRIMARY_NOTHING:
@@ -1696,10 +1678,19 @@
         return mLongPressOnStemPrimaryBehavior != LONG_PRESS_PRIMARY_NOTHING;
     }
 
+    /** Determine whether the device has any stem primary behaviors. */
     private boolean hasStemPrimaryBehavior() {
+        // Read the default stem behaviors from the XML config to determine whether stem primary
+        // behaviors are supported in this build. If they are supported, then the behaviors may be
+        // overridden at runtime through their respective Settings overrides. If they are not
+        // supported, the Settings overrides will not apply.
+        final int defaultShortPressOnStemPrimaryBehavior = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_shortPressOnStemPrimaryBehavior);
+        final int defaultLongPressOnStemPrimaryBehavior = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_longPressOnStemPrimaryBehavior);
         return getMaxMultiPressStemPrimaryCount() > 1
-                || hasLongPressOnStemPrimaryBehavior()
-                || mShortPressOnStemPrimaryBehavior != SHORT_PRESS_PRIMARY_NOTHING;
+                || defaultLongPressOnStemPrimaryBehavior != LONG_PRESS_PRIMARY_NOTHING
+                || defaultShortPressOnStemPrimaryBehavior != SHORT_PRESS_PRIMARY_NOTHING;
     }
 
     private void interceptScreenshotChord(int source, long pressDelay) {
@@ -2787,6 +2778,7 @@
                             KeyEvent.KEYCODE_STEM_PRIMARY,
                             eventTime,
                             () -> {
+                                Slog.d(TAG, "StemPrimaryKeyRule: executing deferred onKeyUp");
                                 // Save the info of the focused task on screen. This may be used
                                 // later to bring the current focused task back to top. For
                                 // example, stem primary triple press enables the A11y interface
@@ -3526,6 +3518,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 +3529,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 +4399,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..ea9ab30 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;
@@ -3948,7 +3949,7 @@
             UserspaceRebootLogger.noteUserspaceRebootWasRequested();
         }
         if (mHandler == null || !mSystemReady) {
-            if (RescueParty.isAttemptingFactoryReset()) {
+            if (RescueParty.isRecoveryTriggeredReboot()) {
                 // If we're stuck in a really low-level reboot loop, and a
                 // rescue party is trying to prompt the user for a factory data
                 // reset, we must GET TO DA CHOPPA!
@@ -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/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index 4bf8a78..afcf49d 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -337,7 +337,7 @@
                             com.android.internal.R.string.reboot_to_update_reboot));
             }
         } else if (mReason != null && mReason.equals(PowerManager.REBOOT_RECOVERY)) {
-            if (RescueParty.isAttemptingFactoryReset()) {
+            if (RescueParty.isRecoveryTriggeredReboot()) {
                 // We're not actually doing a factory reset yet; we're rebooting
                 // to ask the user if they'd like to reset, so give them a less
                 // scary dialog message.
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/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
index 7206c03..ecd140e 100644
--- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
+++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
@@ -89,7 +89,7 @@
 
     @NonNull private final TelephonyManager mTelephonyManager;
     @NonNull private final SubscriptionManager mSubscriptionManager;
-    @NonNull private final CarrierConfigManager mCarrierConfigManager;
+    @Nullable private final CarrierConfigManager mCarrierConfigManager;
 
     @NonNull private final ActiveDataSubscriptionIdListener mActiveDataSubIdListener;
 
@@ -158,8 +158,10 @@
         mSubscriptionManager.addOnSubscriptionsChangedListener(
                 executor, mSubscriptionChangedListener);
         mTelephonyManager.registerTelephonyCallback(executor, mActiveDataSubIdListener);
-        mCarrierConfigManager.registerCarrierConfigChangeListener(executor,
-                mCarrierConfigChangeListener);
+        if (mCarrierConfigManager != null) {
+            mCarrierConfigManager.registerCarrierConfigChangeListener(executor,
+                    mCarrierConfigChangeListener);
+        }
 
         registerCarrierPrivilegesCallbacks();
     }
@@ -200,7 +202,10 @@
         mContext.unregisterReceiver(this);
         mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionChangedListener);
         mTelephonyManager.unregisterTelephonyCallback(mActiveDataSubIdListener);
-        mCarrierConfigManager.unregisterCarrierConfigChangeListener(mCarrierConfigChangeListener);
+        if (mCarrierConfigManager != null) {
+            mCarrierConfigManager.unregisterCarrierConfigChangeListener(
+                    mCarrierConfigChangeListener);
+        }
 
         unregisterCarrierPrivilegesCallbacks();
     }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index 65ab129..7f53ea3 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -336,6 +336,9 @@
                         parser.getAttributeInt(null, "cropRight" + pair.second, 0),
                         parser.getAttributeInt(null, "cropBottom" + pair.second, 0));
                 if (!cropHint.isEmpty()) wallpaper.mCropHints.put(pair.first, cropHint);
+                if (!cropHint.isEmpty() && cropHint.equals(legacyCropHint)) {
+                    wallpaper.mOrientationWhenSet = pair.first;
+                }
             }
             if (wallpaper.mCropHints.size() == 0 && totalCropHint.isEmpty()) {
                 // migration case: the crops per screen orientation are not specified.
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/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..1dcfde4 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;
     }
@@ -3515,6 +3521,9 @@
     }
 
     void enableHighPerfTransition(boolean enable) {
+        if (!mWmService.mSupportsHighPerfTransitions) {
+            return;
+        }
         if (!explicitRefreshRateHints()) {
             if (enable) {
                 getPendingTransaction().setEarlyWakeupStart();
@@ -6835,6 +6844,12 @@
         return mContentRecorder;
     }
 
+    void onMirrorOutputSurfaceOrientationChanged() {
+        if (mContentRecorder != null) {
+            mContentRecorder.onMirrorOutputSurfaceOrientationChanged();
+        }
+    }
+
     /**
      * Pause the recording session.
      */
@@ -7013,9 +7028,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/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index e789fec..16f7373 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -117,6 +117,7 @@
 import android.view.Surface;
 import android.view.View;
 import android.view.ViewDebug;
+import android.view.WindowInsets;
 import android.view.WindowInsets.Type;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowLayout;
@@ -969,19 +970,30 @@
                 break;
 
             case TYPE_BASE_APPLICATION:
-
-                // A non-translucent main app window isn't allowed to fit insets or display cutouts,
-                // as it would create a hole on the display!
                 if (attrs.isFullscreen() && win.mActivityRecord != null
                         && win.mActivityRecord.fillsParent()
-                        && (attrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0
-                        && (attrs.getFitInsetsTypes() != 0
-                                || (attrs.privateFlags & PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED) != 0
-                                        && attrs.layoutInDisplayCutoutMode
-                                                != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS)) {
-                    throw new IllegalArgumentException("Illegal attributes: Main activity window"
-                            + " that isn't translucent trying to fit insets or display cutouts."
-                            + " attrs=" + attrs);
+                        && (attrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0) {
+                    if (attrs.getFitInsetsTypes() != 0) {
+                        // A non-translucent main app window isn't allowed to fit insets,
+                        // as it would create a hole on the display!
+                        throw new IllegalArgumentException("Illegal attributes: Main window of "
+                                + win.mActivityRecord.getName() + " that isn't translucent trying"
+                                + " to fit insets. fitInsetsTypes=" + WindowInsets.Type.toString(
+                                        attrs.getFitInsetsTypes()));
+                    }
+                    if ((attrs.privateFlags & PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED) != 0
+                            && attrs.layoutInDisplayCutoutMode
+                                    != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
+                        // A non-translucent main window of the app enforced to go edge-to-edge
+                        // isn't allowed to fit display cutout, or it will cause software bezels.
+                        throw new IllegalArgumentException("Illegal attributes: Main window of "
+                                + win.mActivityRecord.getName() + " that isn't translucent and"
+                                + " targets SDK level " + win.mActivityRecord.mTargetSdk
+                                + " (>= 35) trying to specify layoutInDisplayCutoutMode as '"
+                                + WindowManager.LayoutParams.layoutInDisplayCutoutModeToString(
+                                        attrs.layoutInDisplayCutoutMode)
+                                + "' instead of 'always'");
+                    }
                 }
                 break;
         }
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/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 34ea4ac..8116f68 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.View.DRAG_FLAG_GLOBAL;
 import static android.view.View.DRAG_FLAG_GLOBAL_SAME_APPLICATION;
 import static android.view.View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG;
@@ -36,6 +37,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.os.Trace;
 import android.util.Slog;
 import android.view.Display;
 import android.view.DragEvent;
@@ -52,6 +54,7 @@
 import com.android.server.wm.WindowManagerInternal.IDragDropCallback;
 
 import java.util.Objects;
+import java.util.Random;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
@@ -386,6 +389,9 @@
                     + "(listener=" + mGlobalDragListener + ", flags=" + mDragState.mFlags + ")");
             return false;
         }
+        final int traceCookie = new Random().nextInt();
+        Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "DragDropController#notifyUnhandledDrop",
+                traceCookie);
         if (DEBUG_DRAG) Slog.d(TAG_WM, "Sending DROP to unhandled listener (" + reason + ")");
         try {
             // Schedule timeout for the unhandled drag listener to call back
@@ -396,6 +402,8 @@
                     if (DEBUG_DRAG) Slog.d(TAG_WM, "Unhandled listener finished handling DROP");
                     synchronized (mService.mGlobalLock) {
                         onUnhandledDropCallback(consumedByListener);
+                        Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER,
+                                "DragDropController#notifyUnhandledDrop", traceCookie);
                     }
                 }
             });
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index b266caa..5ed343a 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -20,6 +20,7 @@
 import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
 import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
 import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
@@ -49,6 +50,7 @@
 import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Slog;
@@ -258,6 +260,7 @@
             }
             mNotifiedWindows.clear();
             mDragInProgress = false;
+            Trace.instant(TRACE_TAG_WINDOW_MANAGER, "DragDropController#DRAG_ENDED");
         }
 
         // Take the cursor back if it has been changed.
@@ -343,6 +346,18 @@
         if (mAnimator != null) {
             return false;
         }
+        try {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "DragDropController#DROP");
+            return reportDropWindowLockInner(token, x, y);
+        } finally {
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+        }
+    }
+
+    private boolean reportDropWindowLockInner(IBinder token, float x, float y) {
+        if (mAnimator != null) {
+            return false;
+        }
 
         final WindowState touchedWin = mService.mInputToWindowMap.get(token);
         final DragEvent unhandledDropEvent = createDropEvent(x, y, null /* touchedWin */,
@@ -355,10 +370,12 @@
                 return true;
             }
 
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "DragDropController#noWindow");
             // "drop" outside a valid window -- no recipient to apply a timeout to, and we can send
             // the drag-ended message immediately.
             endDragLocked(false /* consumed */, false /* relinquishDragSurfaceToDropTarget */);
             if (DEBUG_DRAG) Slog.d(TAG_WM, "Drop outside a valid window " + touchedWin);
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
             return false;
         }
 
@@ -367,6 +384,7 @@
         final IBinder clientToken = touchedWin.mClient.asBinder();
         final DragEvent event = createDropEvent(x, y, touchedWin, false /* includePrivateInfo */);
         try {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "DragDropController#dispatchDrop");
             touchedWin.mClient.dispatchDragEvent(event);
 
             // 5 second timeout for this window to respond to the drop
@@ -380,6 +398,7 @@
             if (MY_PID != touchedWin.mSession.mPid) {
                 event.recycle();
             }
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
         mToken = clientToken;
         mUnhandledDropEvent = unhandledDropEvent;
@@ -471,6 +490,7 @@
     /* call out to each visible window/session informing it about the drag
      */
     void broadcastDragStartedLocked(final float touchX, final float touchY) {
+        Trace.instant(TRACE_TAG_WINDOW_MANAGER, "DragDropController#DRAG_STARTED");
         mOriginalX = mCurrentX = touchX;
         mOriginalY = mCurrentY = touchY;
 
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/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/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 6d8b030..080479d 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2468,7 +2468,15 @@
             for (WindowContainer<?> p = getAnimatableParent(wc); p != null;
                     p = getAnimatableParent(p)) {
                 final ChangeInfo parentChange = changes.get(p);
-                if (parentChange == null || !parentChange.hasChanged()) break;
+                if (parentChange == null) {
+                    break;
+                }
+                if (!parentChange.hasChanged()) {
+                    // In case the target is collected after the parent has been changed, it could
+                    // be too late to snapshot the parent change. Skip to see if there is any
+                    // parent window further up to be considered as change parent.
+                    continue;
+                }
                 if (p.mRemoteToken == null) {
                     // Intermediate parents must be those that has window to be managed by Shell.
                     continue;
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..207b1bb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -538,7 +538,7 @@
     final boolean mHasPermanentDpad;
     final long mDrawLockTimeoutMillis;
     final boolean mAllowAnimationsInLowPowerMode;
-
+    final boolean mSupportsHighPerfTransitions;
     final boolean mAllowBootMessages;
 
     // Indicates whether the Assistant should show on top of the Dream (respectively, above
@@ -1181,6 +1181,8 @@
                 com.android.internal.R.bool.config_allowAnimationsInLowPowerMode);
         mMaxUiWidth = context.getResources().getInteger(
                 com.android.internal.R.integer.config_maxUiWidth);
+        mSupportsHighPerfTransitions = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_deviceSupportsHighPerfTransitions);
         mDisableTransitionAnimation = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_disableTransitionAnimation);
         mPerDisplayFocusEnabled = context.getResources().getBoolean(
@@ -1192,6 +1194,7 @@
         final boolean isScreenSizeDecoupledFromStatusBarAndCutout = context.getResources()
                 .getBoolean(R.bool.config_decoupleStatusBarAndDisplayCutoutFromScreenSize)
                 && mFlags.mAllowsScreenSizeDecoupledFromStatusBarAndCutout;
+
         if (mFlags.mInsetsDecoupledConfiguration) {
             mDecorTypes = 0;
             mConfigTypes = 0;
@@ -8094,15 +8097,6 @@
         }
 
         @Override
-        public void registerKeyguardExitAnimationStartListener(
-                KeyguardExitAnimationStartListener listener) {
-            synchronized (mGlobalLock) {
-                getDefaultDisplayContentLocked().mAppTransition
-                        .registerKeygaurdExitAnimationStartListener(listener);
-            }
-        }
-
-        @Override
         public void reportPasswordChanged(int userId) {
             mKeyguardDisableHandler.updateKeyguardEnabled(userId);
         }
@@ -9098,7 +9092,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 +9121,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 7e6f5ac..d967cde 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1770,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/java/com/android/server/wm/utils/OptPropFactory.java b/services/core/java/com/android/server/wm/utils/OptPropFactory.java
new file mode 100644
index 0000000..8201969b
--- /dev/null
+++ b/services/core/java/com/android/server/wm/utils/OptPropFactory.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.utils;
+
+import static java.lang.Boolean.FALSE;
+import static java.lang.Boolean.TRUE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.content.pm.PackageManager;
+import android.util.Slog;
+
+import java.util.function.BooleanSupplier;
+
+/**
+ * Utility class which helps with handling with properties to opt-in or
+ * opt-out a specific feature.
+ */
+public class OptPropFactory {
+
+    @NonNull
+    private final PackageManager mPackageManager;
+
+    @NonNull
+    private final String mPackageName;
+
+    /**
+     * Object responsible to handle optIn and optOut properties.
+     *
+     * @param packageManager The PackageManager reference
+     * @param packageName    The name of the package.
+     */
+    public OptPropFactory(@NonNull PackageManager packageManager, @NonNull String packageName) {
+        mPackageManager = packageManager;
+        mPackageName = packageName;
+    }
+
+    /**
+     * Creates an OptProp for the given property
+     *
+     * @param propertyName The name of the property.
+     * @return The OptProp for the given property
+     */
+    @NonNull
+    public OptProp create(@NonNull String propertyName) {
+        return OptProp.create(
+                () -> mPackageManager.getProperty(propertyName, mPackageName).getBoolean(),
+                propertyName);
+    }
+
+    /**
+     * Creates an OptProp for the given property behind a gate condition.
+     *
+     * @param propertyName  The name of the property.
+     * @param gateCondition If this resolves to false, the property is unset. This is evaluated at
+     *                      every interaction with the OptProp.
+     * @return The OptProp for the given property
+     */
+    @NonNull
+    public OptProp create(@NonNull String propertyName, @NonNull BooleanSupplier gateCondition) {
+        return OptProp.create(
+                () -> mPackageManager.getProperty(propertyName, mPackageName).getBoolean(),
+                propertyName,
+                gateCondition);
+    }
+
+    @FunctionalInterface
+    private interface ThrowableBooleanSupplier {
+        boolean get() throws Exception;
+    }
+
+    public static class OptProp {
+
+        private static final int VALUE_UNSET = -2;
+        private static final int VALUE_UNDEFINED = -1;
+        private static final int VALUE_FALSE = 0;
+        private static final int VALUE_TRUE = 1;
+
+        @IntDef(prefix = {"VALUE_"}, value = {
+                VALUE_UNSET,
+                VALUE_UNDEFINED,
+                VALUE_FALSE,
+                VALUE_TRUE,
+        })
+        @interface OptionalValue {}
+
+        private static final String TAG = "OptProp";
+
+        // The condition is evaluated every time the OptProp state is accessed.
+        @NonNull
+        private final BooleanSupplier mCondition;
+
+        // This is evaluated only once in the lifetime of an OptProp.
+        @NonNull
+        private final ThrowableBooleanSupplier mValueSupplier;
+
+        @NonNull
+        private final String mPropertyName;
+
+        @OptionalValue
+        private int mValue = VALUE_UNDEFINED;
+
+        private OptProp(@NonNull ThrowableBooleanSupplier valueSupplier,
+                @NonNull String propertyName,
+                @NonNull BooleanSupplier condition) {
+            mValueSupplier = valueSupplier;
+            mPropertyName = propertyName;
+            mCondition = condition;
+        }
+
+        @NonNull
+        private static OptProp create(@NonNull ThrowableBooleanSupplier valueSupplier,
+                @NonNull String propertyName) {
+            return new OptProp(valueSupplier, propertyName, () -> true);
+        }
+
+        @NonNull
+        private static OptProp create(@NonNull ThrowableBooleanSupplier valueSupplier,
+                @NonNull String propertyName, @NonNull BooleanSupplier condition) {
+            return new OptProp(valueSupplier, propertyName, condition);
+        }
+
+        /**
+         * @return {@code true} when the guarding condition is {@code true} and the property has
+         * been explicitly set to {@code true}. {@code false} otherwise. The guarding condition is
+         * evaluated every time this method is invoked.
+         */
+        public boolean isTrue() {
+            return mCondition.getAsBoolean() && getValue() == VALUE_TRUE;
+        }
+
+        /**
+         * @return {@code true} when the guarding condition is {@code true} and the property has
+         * been explicitly set to {@code false}. {@code false} otherwise. The guarding condition is
+         * evaluated every time this method is invoked.
+         */
+        public boolean isFalse() {
+            return mCondition.getAsBoolean() && getValue() == VALUE_FALSE;
+        }
+
+        /**
+         * Returns {@code true} when the following conditions are met:
+         * <ul>
+         *     <li>{@code gatingCondition} doesn't evaluate to {@code false}
+         *     <li>App developers didn't opt out with a component {@code property}
+         *     <li>App developers opted in with a component {@code property} or an OEM opted in with
+         *     a per-app override
+         * </ul>
+         *
+         * <p>This is used for the treatments that are enabled only on per-app basis.
+         */
+        public boolean shouldEnableWithOverrideAndProperty(boolean overrideValue) {
+            if (!mCondition.getAsBoolean()) {
+                return false;
+            }
+            if (getValue() == VALUE_FALSE) {
+                return false;
+            }
+            return getValue() == VALUE_TRUE || overrideValue;
+        }
+
+        /**
+         * Returns {@code true} when the following conditions are met:
+         * <ul>
+         *     <li>{@code gatingCondition} doesn't evaluate to {@code false}
+         *     <li>App developers didn't opt out with a component {@code property}
+         *     <li>OEM opted in with a per-app override
+         * </ul>
+         *
+         * <p>This is used for the treatments that are enabled based with the heuristic but can be
+         * disabled on per-app basis by OEMs or app developers.
+         */
+        public boolean shouldEnableWithOptInOverrideAndOptOutProperty(
+                boolean overrideValue) {
+            if (!mCondition.getAsBoolean()) {
+                return false;
+            }
+            return getValue() != VALUE_FALSE && overrideValue;
+        }
+
+        /**
+         * Returns {@code true} when the following conditions are met:
+         * <ul>
+         *     <li>{@code gatingCondition} doesn't resolve to {@code false}
+         *     <li>OEM didn't opt out with a per-app override
+         *     <li>App developers didn't opt out with a component {@code property}
+         * </ul>
+         *
+         * <p>This is used for the treatments that are enabled based with the heuristic but can be
+         * disabled on per-app basis by OEMs or app developers.
+         */
+        public boolean shouldEnableWithOptOutOverrideAndProperty(boolean overrideValue) {
+            if (!mCondition.getAsBoolean()) {
+                return false;
+            }
+            return getValue() != VALUE_FALSE && !overrideValue;
+        }
+
+        @OptionalValue
+        private int getValue() {
+            if (mValue == VALUE_UNDEFINED) {
+                try {
+                    final Boolean value = mValueSupplier.get();
+                    if (TRUE.equals(value)) {
+                        mValue = VALUE_TRUE;
+                    } else if (FALSE.equals(value)) {
+                        mValue = VALUE_FALSE;
+                    } else {
+                        mValue = VALUE_UNSET;
+                    }
+                } catch (Exception e) {
+                    Slog.w(TAG, "Cannot read opt property " + mPropertyName);
+                    mValue = VALUE_UNSET;
+                }
+            }
+            return mValue;
+        }
+    }
+}
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/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 c6189ed..3b2a3dd 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -408,6 +408,8 @@
             "com.android.server.searchui.SearchUiManagerService";
     private static final String SMARTSPACE_MANAGER_SERVICE_CLASS =
             "com.android.server.smartspace.SmartspaceManagerService";
+    private static final String CONTEXTUAL_SEARCH_MANAGER_SERVICE_CLASS =
+            "com.android.server.contextualsearch.ContextualSearchManagerService";
     private static final String DEVICE_IDLE_CONTROLLER_CLASS =
             "com.android.server.DeviceIdleController";
     private static final String BLOB_STORE_MANAGER_SERVICE_CLASS =
@@ -2016,6 +2018,16 @@
                 Slog.d(TAG, "SmartspaceManagerService not defined by OEM or disabled by flag");
             }
 
+            // Contextual search manager service
+            if (deviceHasConfigString(context,
+                    R.string.config_defaultContextualSearchPackageName)) {
+                t.traceBegin("StartContextualSearchService");
+                mSystemServiceManager.startService(CONTEXTUAL_SEARCH_MANAGER_SERVICE_CLASS);
+                t.traceEnd();
+            } else {
+                Slog.d(TAG, "ContextualSearchManagerService not defined or disabled by flag");
+            }
+
             t.traceBegin("InitConnectivityModuleConnector");
             try {
                 ConnectivityModuleConnector.getInstance().init(context);
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index af8ce31..7618740 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -268,14 +268,33 @@
         } else {
             newFlags = newFlags andInv PermissionFlags.RESTRICTION_REVOKED
         }
-        newFlags =
-            if (
-                permission.isSoftRestricted && !isExempt &&
-                    !anyPackageInAppId(appId) {
-                        permissionName in it.androidPackage!!.requestedPermissions &&
-                            isSoftRestrictedPermissionExemptForPackage(it, permissionName)
+        val isSoftRestricted =
+            if (permission.isSoftRestricted && !isExempt) {
+                val targetSdkVersion =
+                    reducePackageInAppId(appId, Build.VERSION_CODES.CUR_DEVELOPMENT) {
+                        targetSdkVersion,
+                        packageState ->
+                        if (permissionName in packageState.androidPackage!!.requestedPermissions) {
+                            targetSdkVersion.coerceAtMost(
+                                packageState.androidPackage!!.targetSdkVersion
+                            )
+                        } else {
+                            targetSdkVersion
+                        }
                     }
-            ) {
+                !anyPackageInAppId(appId) {
+                    permissionName in it.androidPackage!!.requestedPermissions &&
+                        isSoftRestrictedPermissionExemptForPackage(
+                            it,
+                            targetSdkVersion,
+                            permissionName
+                        )
+                }
+            } else {
+                false
+            }
+        newFlags =
+            if (isSoftRestricted) {
                 newFlags or PermissionFlags.SOFT_RESTRICTED
             } else {
                 newFlags andInv PermissionFlags.SOFT_RESTRICTED
@@ -1159,9 +1178,14 @@
                 }
             newFlags =
                 if (
-                    permission.isSoftRestricted && !isExempt &&
+                    permission.isSoftRestricted &&
+                        !isExempt &&
                         !requestingPackageStates.anyIndexed { _, it ->
-                            isSoftRestrictedPermissionExemptForPackage(it, permissionName)
+                            isSoftRestrictedPermissionExemptForPackage(
+                                it,
+                                targetSdkVersion,
+                                permissionName
+                            )
                         }
                 ) {
                     newFlags or PermissionFlags.SOFT_RESTRICTED
@@ -1444,13 +1468,20 @@
     }
 
     // See also SoftRestrictedPermissionPolicy.mayGrantPermission()
+    // Note: we need the appIdTargetSdkVersion parameter here because we are OR-ing the exempt
+    // status for all packages in a shared UID, but the storage soft restriction logic needs to NOT
+    // exempt when the target SDK version is low, which is the opposite of what most of our code do,
+    // and thus can't check the individual package's target SDK version and rely on the OR among
+    // them.
     private fun isSoftRestrictedPermissionExemptForPackage(
         packageState: PackageState,
+        appIdTargetSdkVersion: Int,
         permissionName: String
     ): Boolean =
         when (permissionName) {
-            Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE ->
-                packageState.androidPackage!!.targetSdkVersion >= Build.VERSION_CODES.Q
+            Manifest.permission.READ_EXTERNAL_STORAGE,
+            Manifest.permission.WRITE_EXTERNAL_STORAGE ->
+                appIdTargetSdkVersion >= Build.VERSION_CODES.Q
             else -> false
         }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/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 18f0311..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);
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/dreamservicetests/res/xml/test_dream_metadata.xml b/services/tests/dreamservicetests/res/xml/test_dream_metadata.xml
index 9211ec1..6e7d9ad 100644
--- a/services/tests/dreamservicetests/res/xml/test_dream_metadata.xml
+++ b/services/tests/dreamservicetests/res/xml/test_dream_metadata.xml
@@ -17,4 +17,6 @@
 <dream xmlns:android="http://schemas.android.com/apk/res/android"
        android:settingsActivity=
            "com.android.frameworks.dreamservicetests/.TestDreamSettingsActivity"
-       android:showClockAndComplications="false" />
+       android:showClockAndComplications="false"
+       android:dreamCategory="home_panel"
+       />
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
index d231e40f..293ab7b 100644
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
@@ -48,6 +48,7 @@
         assertThat(metadata.settingsActivity).isEqualTo(
                 ComponentName.unflattenFromString(testSettingsActivity));
         assertFalse(metadata.showComplications);
+        assertThat(metadata.dreamCategory).isEqualTo(DreamService.DREAM_CATEGORY_HOME_PANEL);
     }
 
     @Test
@@ -58,6 +59,7 @@
         final DreamService.DreamMetadata metadata = getDreamMetadata(testDreamClassName);
 
         assertThat(metadata.settingsActivity).isNull();
+        assertThat(metadata.dreamCategory).isEqualTo(DreamService.DREAM_CATEGORY_DEFAULT);
     }
 
     private DreamService.DreamMetadata getDreamMetadata(String dreamClassName)
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index 211a83d..c30ac2d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -439,19 +439,19 @@
     }
 
     @Test
-    public void testIsAttemptingFactoryReset() {
+    public void testIsRecoveryTriggeredReboot() {
         for (int i = 0; i < LEVEL_FACTORY_RESET; i++) {
             noteBoot(i + 1);
         }
         assertFalse(RescueParty.isFactoryResetPropertySet());
         setCrashRecoveryPropAttemptingReboot(false);
         noteBoot(LEVEL_FACTORY_RESET + 1);
-        assertTrue(RescueParty.isAttemptingFactoryReset());
+        assertTrue(RescueParty.isRecoveryTriggeredReboot());
         assertTrue(RescueParty.isFactoryResetPropertySet());
     }
 
     @Test
-    public void testIsAttemptingFactoryResetOnlyAfterRebootCompleted() {
+    public void testIsRecoveryTriggeredRebootOnlyAfterRebootCompleted() {
         for (int i = 0; i < LEVEL_FACTORY_RESET; i++) {
             noteBoot(i + 1);
         }
@@ -464,7 +464,7 @@
         noteBoot(mitigationCount++);
         setCrashRecoveryPropAttemptingReboot(false);
         noteBoot(mitigationCount + 1);
-        assertTrue(RescueParty.isAttemptingFactoryReset());
+        assertTrue(RescueParty.isRecoveryTriggeredReboot());
         assertTrue(RescueParty.isFactoryResetPropertySet());
     }
 
@@ -477,7 +477,7 @@
         for (int i = 1; i <= LEVEL_FACTORY_RESET; i++) {
             noteBoot(i);
         }
-        assertFalse(RescueParty.isAttemptingFactoryReset());
+        assertFalse(RescueParty.isRecoveryTriggeredReboot());
     }
 
     @Test
@@ -489,7 +489,7 @@
         for (int i = 0; i <= LEVEL_FACTORY_RESET; i++) {
             noteAppCrash(i + 1, true);
         }
-        assertFalse(RescueParty.isAttemptingFactoryReset());
+        assertFalse(RescueParty.isRecoveryTriggeredReboot());
     }
 
     @Test
@@ -501,7 +501,7 @@
         for (int i = 1; i <= LEVEL_FACTORY_RESET; i++) {
             noteBoot(i);
         }
-        assertTrue(RescueParty.isAttemptingFactoryReset());
+        assertTrue(RescueParty.isRecoveryTriggeredReboot());
     }
     @Test
     public void testNotThrottlingAfterTimeoutOnAppCrash() {
@@ -512,7 +512,7 @@
         for (int i = 0; i <= LEVEL_FACTORY_RESET; i++) {
             noteAppCrash(i + 1, true);
         }
-        assertTrue(RescueParty.isAttemptingFactoryReset());
+        assertTrue(RescueParty.isRecoveryTriggeredReboot());
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index fcb3caa..dc5b00a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -28,10 +28,15 @@
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
 import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
 import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
+import static android.os.PowerExemptionManager.REASON_DENIED;
 import static android.util.DebugUtils.valueToString;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 import static com.android.server.am.ActivityManagerInternalTest.CustomThread;
 import static com.android.server.am.ActivityManagerService.Injector;
 import static com.android.server.am.ProcessList.NETWORK_STATE_BLOCK;
@@ -52,28 +57,40 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.clearInvocations;
 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.timeout;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
 
 import android.Manifest;
 import android.app.ActivityManager;
+import android.app.AppGlobals;
 import android.app.AppOpsManager;
+import android.app.BackgroundStartPrivileges;
 import android.app.BroadcastOptions;
+import android.app.ForegroundServiceDelegationOptions;
 import android.app.IApplicationThread;
 import android.app.IUidObserver;
+import android.app.Notification;
+import android.app.NotificationChannel;
 import android.app.SyncNotedAppOp;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.ServiceConnection;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.ServiceInfo;
+import android.graphics.drawable.Icon;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -84,6 +101,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.permission.IPermissionManager;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
@@ -105,18 +123,20 @@
 import com.android.server.am.ProcessList.IsolatedUidRangeAllocator;
 import com.android.server.am.UidObserverController.ChangeRecord;
 import com.android.server.appop.AppOpsService;
+import com.android.server.notification.NotificationManagerInternal;
+import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.ActivityTaskManagerService;
 
 import org.junit.After;
-import org.junit.AfterClass;
 import org.junit.Before;
-import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+import org.mockito.verification.VerificationMode;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -127,13 +147,15 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
 
 /**
  * Test class for {@link ActivityManagerService}.
  *
  * Build/Install/Run:
- *  atest FrameworksServicesTests:ActivityManagerServiceTest
+ *  atest FrameworksMockingServicesTests:ActivityManagerServiceTest
  */
 @Presubmit
 @SmallTest
@@ -148,6 +170,9 @@
 
     private static final String TEST_EXTRA_KEY1 = "com.android.server.am.TEST_EXTRA_KEY1";
     private static final String TEST_EXTRA_VALUE1 = "com.android.server.am.TEST_EXTRA_VALUE1";
+
+    private static final String TEST_PACKAGE_NAME = "com.android.server.am.testpackage";
+
     private static final String PROPERTY_APPLY_SDK_SANDBOX_AUDIT_RESTRICTIONS =
             "apply_sdk_sandbox_audit_restrictions";
     private static final String PROPERTY_APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS =
@@ -155,6 +180,7 @@
     private static final String APPLY_SDK_SANDBOX_AUDIT_RESTRICTIONS = ":isSdkSandboxAudit";
     private static final String APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS = ":isSdkSandboxNext";
     private static final int TEST_UID = 11111;
+    private static final int TEST_PID = 22222;
     private static final int USER_ID = 666;
 
     private static final long TEST_PROC_STATE_SEQ1 = 555;
@@ -169,22 +195,8 @@
         UidRecord.CHANGE_CAPABILITY,
     };
 
-    private static PackageManagerInternal sPackageManagerInternal;
     private static ProcessList.ProcessListSettingsListener sProcessListSettingsListener;
 
-    @BeforeClass
-    public static void setUpOnce() {
-        sPackageManagerInternal = mock(PackageManagerInternal.class);
-        doReturn(new ComponentName("", "")).when(sPackageManagerInternal)
-                .getSystemUiServiceComponent();
-        LocalServices.addService(PackageManagerInternal.class, sPackageManagerInternal);
-    }
-
-    @AfterClass
-    public static void tearDownOnce() {
-        LocalServices.removeServiceForTest(PackageManagerInternal.class);
-    }
-
     @Rule
     public final ApplicationExitInfoTest.ServiceThreadRule
             mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule();
@@ -196,15 +208,41 @@
 
     @Mock private AppOpsService mAppOpsService;
     @Mock private UserController mUserController;
+    @Mock private IPackageManager mPackageManager;
+    @Mock private IPermissionManager mPermissionManager;
+    @Mock private BatteryStatsService mBatteryStatsService;
+    @Mock private PackageManagerInternal mPackageManagerInternal;
+    @Mock private ActivityTaskManagerInternal mActivityTaskManagerInternal;
+    @Mock private NotificationManagerInternal mNotificationManagerInternal;
+
 
     private TestInjector mInjector;
     private ActivityManagerService mAms;
+    private ActiveServices mActiveServices;
     private HandlerThread mHandlerThread;
     private TestHandler mHandler;
+    private MockitoSession mMockingSession;
+
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
+        mMockingSession = mockitoSession()
+                .initMocks(this)
+                .mockStatic(AppGlobals.class)
+                .strictness(Strictness.LENIENT)
+                .startMocking();
         MockitoAnnotations.initMocks(this);
 
+        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
+        LocalServices.addService(ActivityTaskManagerInternal.class, mActivityTaskManagerInternal);
+        LocalServices.addService(NotificationManagerInternal.class, mNotificationManagerInternal);
+
+        doReturn(new ComponentName("", "")).when(mPackageManagerInternal)
+                .getSystemUiServiceComponent();
+
+        doReturn(mPackageManager).when(AppGlobals::getPackageManager);
+        doReturn(mPermissionManager).when(AppGlobals::getPermissionManager);
+        doReturn(new String[]{""}).when(mPackageManager).getPackagesForUid(eq(Process.myUid()));
+
         mHandlerThread = new HandlerThread(TAG);
         mHandlerThread.start();
         mHandler = new TestHandler(mHandlerThread.getLooper());
@@ -220,6 +258,7 @@
         mAms.mConstants.mNetworkAccessTimeoutMs = 2000;
         mAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
         mAms.mActivityTaskManager.initialize(null, null, mHandler.getLooper());
+        mAms.mAtmInternal = mActivityTaskManagerInternal;
         mHandler.setRunnablesToIgnore(
                 List.of(mAms.mUidObserverController.getDispatchRunnableForTest()));
 
@@ -250,6 +289,15 @@
         if (sProcessListSettingsListener != null) {
             sProcessListSettingsListener.unregisterObserver();
         }
+        clearInvocations(mNotificationManagerInternal);
+
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
+        LocalServices.removeServiceForTest(NotificationManagerInternal.class);
+
+        if (mMockingSession != null) {
+            mMockingSession.finishMocking();
+        }
     }
 
     @SuppressWarnings("GuardedBy")
@@ -445,6 +493,7 @@
         }
     }
 
+    @SuppressWarnings("GuardedBy")
     private UidRecord addUidRecord(int uid) {
         final UidRecord uidRec = new UidRecord(uid, mAms);
         uidRec.procStateSeqWaitingForNetwork = 1;
@@ -453,10 +502,13 @@
 
         ApplicationInfo info = new ApplicationInfo();
         info.packageName = "";
+        info.uid = uid;
 
         final ProcessRecord appRec = new ProcessRecord(mAms, info, TAG, uid);
-        final ProcessStatsService tracker = new ProcessStatsService(mAms, mContext.getCacheDir());
-        appRec.makeActive(mock(IApplicationThread.class), tracker);
+        final ProcessStatsService tracker = mAms.mProcessStats;
+        final IApplicationThread appThread = mock(IApplicationThread.class);
+        doReturn(mock(IBinder.class)).when(appThread).asBinder();
+        appRec.makeActive(appThread, tracker);
         mAms.mProcessList.getLruProcessesLSP().add(appRec);
 
         return uidRec;
@@ -1209,6 +1261,108 @@
         mAms.mUidObserverController.getPendingUidChangesForTest().clear();
     }
 
+    @Test
+    public void testStartForegroundServiceDelegateWithNotification() throws Exception {
+        testStartForegroundServiceDelegate(true);
+    }
+
+    @Test
+    public void testStartForegroundServiceDelegateWithoutNotification() throws Exception {
+        testStartForegroundServiceDelegate(false);
+    }
+
+    @SuppressWarnings("GuardedBy")
+    private void testStartForegroundServiceDelegate(boolean withNotification) throws Exception {
+        mockNoteOperation();
+
+        final int notificationId = 42;
+        final Notification notification = mock(Notification.class);
+
+        addUidRecord(TEST_UID);
+        final ProcessRecord app = mAms.mProcessList.getLruProcessesLSP().get(0);
+        app.mPid = TEST_PID;
+        app.info.packageName = TEST_PACKAGE_NAME;
+        app.info.processName = TEST_PACKAGE_NAME;
+
+        doReturn(app.info).when(mPackageManager).getApplicationInfo(
+                eq(app.info.packageName), anyLong(), anyInt());
+
+        doReturn(true).when(mActiveServices)
+                .canStartForegroundServiceLocked(anyInt(), anyInt(), anyString());
+        doReturn(REASON_DENIED).when(mActiveServices)
+                .shouldAllowFgsWhileInUsePermissionLocked(anyString(), anyInt(), anyInt(),
+                        any(ProcessRecord.class), any(BackgroundStartPrivileges.class));
+
+        doReturn(true).when(mNotificationManagerInternal).areNotificationsEnabledForPackage(
+                anyString(), anyInt());
+        doReturn(mock(Icon.class)).when(notification).getSmallIcon();
+        doReturn("").when(notification).getChannelId();
+        doReturn(mock(NotificationChannel.class)).when(mNotificationManagerInternal)
+                .getNotificationChannel(anyString(), anyInt(), anyString());
+
+        mAms.mAppProfiler.mCachedAppsWatermarkData.mCachedAppHighWatermark = Integer.MAX_VALUE;
+
+        final ForegroundServiceDelegationOptions.Builder optionsBuilder =
+                new ForegroundServiceDelegationOptions.Builder()
+                .setClientPid(app.mPid)
+                .setClientUid(app.uid)
+                .setClientPackageName(app.info.packageName)
+                .setClientAppThread(app.getThread())
+                .setSticky(false)
+                .setClientInstanceName(
+                        "SystemExemptedFgsDelegate_"
+                        + Process.myUid()
+                        + "_"
+                        + app.uid
+                        + "_"
+                        + app.info.packageName)
+                .setForegroundServiceTypes(ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED)
+                .setDelegationService(
+                        ForegroundServiceDelegationOptions.DELEGATION_SERVICE_SYSTEM_EXEMPTED);
+        if (withNotification) {
+            optionsBuilder.setClientNotification(notificationId, notification);
+        }
+        final ForegroundServiceDelegationOptions options = optionsBuilder.build();
+
+        final CountDownLatch[] latchHolder = new CountDownLatch[1];
+        final ServiceConnection conn = new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder service) {
+                latchHolder[0].countDown();
+            }
+
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+                latchHolder[0].countDown();
+            }
+        };
+
+        latchHolder[0] = new CountDownLatch(1);
+        mAms.mInternal.startForegroundServiceDelegate(options, conn);
+
+        assertThat(latchHolder[0].await(5, TimeUnit.SECONDS)).isTrue();
+        assertEquals(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE,
+                app.mState.getCurProcState());
+        final long timeoutMs = 5000L;
+        final VerificationMode mode = withNotification
+                ? timeout(timeoutMs) : after(timeoutMs).atMost(0);
+        verify(mNotificationManagerInternal, mode)
+                .enqueueNotification(eq(app.info.packageName), eq(app.info.packageName),
+                        eq(app.info.uid), eq(app.mPid), eq(null),
+                        eq(notificationId), eq(notification), anyInt(), eq(true));
+
+        latchHolder[0] = new CountDownLatch(1);
+        mAms.mInternal.stopForegroundServiceDelegate(options);
+
+        assertThat(latchHolder[0].await(5, TimeUnit.SECONDS)).isTrue();
+        assertEquals(ActivityManager.PROCESS_STATE_CACHED_EMPTY,
+                app.mState.getCurProcState());
+        verify(mNotificationManagerInternal, mode)
+                .cancelNotification(eq(app.info.packageName), eq(app.info.packageName),
+                        eq(app.info.uid), eq(app.mPid), eq(null),
+                        eq(notificationId), anyInt());
+    }
+
     private static class TestHandler extends Handler {
         private static final long WAIT_FOR_MSG_TIMEOUT_MS = 4000; // 4 sec
         private static final long WAIT_FOR_MSG_INTERVAL_MS = 400; // 0.4 sec
@@ -1291,6 +1445,19 @@
             usersStartedOnSecondaryDisplays.add(new Pair<>(userId, displayId));
             return returnValueForstartUserOnSecondaryDisplay;
         }
+
+        @Override
+        public ActiveServices getActiveServices(ActivityManagerService service) {
+            if (mActiveServices == null) {
+                mActiveServices = spy(new ActiveServices(service));
+            }
+            return mActiveServices;
+        }
+
+        @Override
+        public BatteryStatsService getBatteryStatsService() {
+            return mBatteryStatsService;
+        }
     }
 
     // TODO: [b/302724778] Remove manual JNI load
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
index ce281da..bd20ae2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
@@ -56,7 +56,6 @@
 
 import org.junit.Rule;
 import org.mockito.Mock;
-import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 import java.io.File;
@@ -161,7 +160,6 @@
         realAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
         realAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
         realAms.mAtmInternal = spy(realAms.mActivityTaskManager.getAtmInternal());
-        realAms.mOomAdjuster.mCachedAppOptimizer = Mockito.mock(CachedAppOptimizer.class);
         realAms.mOomAdjuster = spy(realAms.mOomAdjuster);
         ExtendedMockito.doNothing().when(() -> ProcessList.setOomAdj(anyInt(), anyInt(), anyInt()));
         realAms.mPackageManagerInt = mPackageManagerInt;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 1172685..66ab807 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -86,6 +86,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.mockito.ArgumentMatcher;
 import org.mockito.InOrder;
@@ -2334,8 +2335,8 @@
                 .isGreaterThan(getReceiverScheduledTime(prioritizedRecord, receiverBlue));
     }
 
+    @Ignore
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_DEFER_OUTGOING_BCASTS)
     public void testDeferOutgoingBroadcasts() throws Exception {
         final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
         setProcessFreezable(callerApp, true /* pendingFreeze */, false /* frozen */);
@@ -2349,8 +2350,6 @@
                 makeRegisteredReceiver(receiverGreenApp),
                 makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
                 makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW))));
-        // Verify that we invoke the call to freeze the caller app.
-        verify(mAms.mOomAdjuster.mCachedAppOptimizer).freezeAppAsyncImmediateLSP(callerApp);
 
         waitForIdle();
         verifyScheduleRegisteredReceiver(never(), receiverGreenApp, timeTick);
diff --git a/services/tests/mockingservicestests/src/com/android/server/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/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..b2ecea1 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;
@@ -812,7 +813,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_DISABLE_CONTINUOUS_SHORTCUT_ON_FORCE_STOP)
     public void testPackagesForceStopped_fromContinuousService_removesButtonTarget() {
         final AccessibilityServiceInfo info_a = new AccessibilityServiceInfo();
         info_a.setComponentName(COMPONENT_NAME);
@@ -1318,6 +1318,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 +1513,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/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/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index 67b131f..1249707 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -206,7 +206,6 @@
 import org.junit.After;
 import org.junit.Assume;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.MethodRule;
@@ -2151,14 +2150,12 @@
         assertFalse(mService.isUidNetworkingBlocked(UID_E, false));
     }
 
-    @Ignore("Temporarily disabled until the feature is enabled")
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
     public void testBackgroundChainEnabled() throws Exception {
         verify(mNetworkManager).setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, true);
     }
 
-    @Ignore("Temporarily disabled until the feature is enabled")
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
     public void testBackgroundChainOnProcStateChange() throws Exception {
@@ -2188,7 +2185,6 @@
         assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
     }
 
-    @Ignore("Temporarily disabled until the feature is enabled")
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
     public void testBackgroundChainOnAllowlistChange() throws Exception {
@@ -2227,7 +2223,6 @@
         assertFalse(mService.isUidNetworkingBlocked(UID_B, false));
     }
 
-    @Ignore("Temporarily disabled until the feature is enabled")
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
     public void testBackgroundChainOnTempAllowlistChange() throws Exception {
@@ -2266,7 +2261,6 @@
                 && uidState.procState == procState && uidState.capability == capability;
     }
 
-    @Ignore("Temporarily disabled until the feature is enabled")
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
     public void testUidObserverFiltersProcStateChanges() throws Exception {
@@ -2329,7 +2323,6 @@
         waitForUidEventHandlerIdle();
     }
 
-    @Ignore("Temporarily disabled until the feature is enabled")
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
     public void testUidObserverFiltersStaleChanges() throws Exception {
@@ -2350,7 +2343,6 @@
         waitForUidEventHandlerIdle();
     }
 
-    @Ignore("Temporarily disabled until the feature is enabled")
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
     public void testUidObserverFiltersCapabilityChanges() throws Exception {
@@ -2430,7 +2422,6 @@
         assertFalse(mService.isUidNetworkingBlocked(UID_A, false));
     }
 
-    @Ignore("Temporarily disabled until the feature is enabled")
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
     public void testObsoleteHandleUidChanged() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
index e249cd7..55c48e0 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
@@ -90,8 +90,6 @@
     public void testCanDeviceOwnerChange() {
         assertFalse(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_RECORD_AUDIO));
         assertFalse(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_WALLPAPER));
-        assertFalse(UserRestrictionsUtils.canDeviceOwnerChange(
-                UserManager.DISALLOW_ADD_PRIVATE_PROFILE));
         assertTrue(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_ADD_USER));
         assertTrue(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_USER_SWITCH));
     }
@@ -110,10 +108,6 @@
                 UserManager.DISALLOW_USER_SWITCH,
                 true,
                 false));
-        assertFalse(UserRestrictionsUtils.canProfileOwnerChange(
-                UserManager.DISALLOW_ADD_PRIVATE_PROFILE,
-                true,
-                false));
         assertTrue(UserRestrictionsUtils.canProfileOwnerChange(
                 UserManager.DISALLOW_ADD_USER,
                 true,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index f29d215..99ab405 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -325,7 +325,6 @@
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
-import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -13226,35 +13225,6 @@
     }
 
     @Test
-    public void fixNotification_customAllowlistToken()
-            throws Exception {
-        Notification n = new Notification.Builder(mContext, "test")
-                .build();
-        try {
-            Field allowlistToken = Class.forName("android.app.Notification").
-                    getDeclaredField("mAllowlistToken");
-            allowlistToken.setAccessible(true);
-            allowlistToken.set(n, new Binder());
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-
-        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
-
-        IBinder actual = null;
-        try {
-            Field allowlistToken = Class.forName("android.app.Notification").
-                    getDeclaredField("mAllowlistToken");
-            allowlistToken.setAccessible(true);
-            actual = (IBinder) allowlistToken.get(n);
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-
-        assertTrue(mService.ALLOWLIST_TOKEN == actual);
-    }
-
-    @Test
     public void testCancelAllNotifications_IgnoreUserInitiatedJob() throws Exception {
         when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
                 .thenReturn(true);
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
index fee6582..dff4984 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -43,9 +43,12 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
 import static com.android.server.policy.WindowManagerPolicy.ACTION_PASS_TO_USER;
 
+import static org.mockito.ArgumentMatchers.eq;
+
 import static java.util.Collections.unmodifiableMap;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.ArrayMap;
 import android.view.InputDevice;
@@ -57,6 +60,7 @@
 import com.android.internal.util.test.FakeSettingsProviderRule;
 
 import org.junit.After;
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.rules.RuleChain;
 
@@ -70,9 +74,10 @@
     @Rule
     public RuleChain rules = RuleChain.outerRule(mSettingsProviderRule).around(mSetFlagsRule);
 
+    private Resources mResources;
     TestPhoneWindowManager mPhoneWindowManager;
     DispatchedKeyHandler mDispatchedKeyHandler = event -> false;
-    final Context mContext = spy(getInstrumentation().getTargetContext());
+    Context mContext;
 
     /** Modifier key to meta state */
     protected static final Map<Integer, Integer> MODIFIER;
@@ -90,6 +95,16 @@
         MODIFIER = unmodifiableMap(map);
     }
 
+    @Before
+    public void setup() {
+        mContext = spy(getInstrumentation().getTargetContext());
+        mResources = spy(mContext.getResources());
+        doReturn(mResources).when(mContext).getResources();
+        doReturn(mSettingsProviderRule.mockContentResolver(mContext))
+                .when(mContext).getContentResolver();
+    }
+
+
     /** Same as {@link setUpPhoneWindowManager(boolean)}, without supporting settings update. */
     protected final void setUpPhoneWindowManager() {
         setUpPhoneWindowManager(/* supportSettingsUpdate= */ false);
@@ -101,12 +116,14 @@
      * <p>Subclasses must call this at the start of the test if they intend to interact with phone
      * window manager.
      *
-     * @param supportSettingsUpdate {@code true} if this test should read and listen to provider
-     *      settings values.
+     * @param supportSettingsUpdate {@code true} to have PWM respond to any Settings changes upon
+     *    instantiation. Although this is supposed to also allow a test to listen to any Settings
+     *    changes after instantiation, MockContentResolver in this class's setup stubs out
+     *    notifyChange(), which prevents SettingsObserver from getting notified of events. So
+     *    we're effectively always instantiating TestPhoneWindowManager with
+     *    supportSettingsUpdate=false.
      */
     protected final void setUpPhoneWindowManager(boolean supportSettingsUpdate) {
-        doReturn(mSettingsProviderRule.mockContentResolver(mContext))
-                .when(mContext).getContentResolver();
         mPhoneWindowManager = new TestPhoneWindowManager(mContext, supportSettingsUpdate);
     }
 
@@ -187,6 +204,23 @@
         sendKeyCombination(new int[]{keyCode}, 0 /*durationMillis*/, longPress, DEFAULT_DISPLAY);
     }
 
+    /**
+     * Since we use SettingsProviderRule to mock the ContentResolver in these
+     * tests, the settings observer registered by PhoneWindowManager will not
+     * be triggered automatically by the mock. Use this method to force the
+     * settings observer change after modifying any settings.
+     */
+    void triggerSettingsObserverChange() {
+        mPhoneWindowManager.getSettingsObserver().onChange(
+                // This boolean doesn't matter. This observer does the same thing regardless.
+                /*selfChange=*/true);
+    }
+
+    /** Override a resource's return value. */
+    void overrideResource(int resId, int expectedBehavior) {
+        doReturn(expectedBehavior).when(mResources).getInteger(eq(resId));
+    }
+
     private void interceptKey(KeyEvent keyEvent) {
         int actions = mPhoneWindowManager.interceptKeyBeforeQueueing(keyEvent);
         if ((actions & ACTION_PASS_TO_USER) != 0) {
diff --git a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
index 77e7a0a..2e85025 100644
--- a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
@@ -291,6 +291,101 @@
         mPhoneWindowManager.assertSwitchToTask(referenceId);
     }
 
+    /**
+     * Ensure the stem rule is added even when button behaviors are set to nothing.
+     *
+     * This makes sure that if stem key behaviors are overridden to NOTHING, then we check the
+     * XML config as the source of truth upon reboot to see whether a device should have a stem
+     * key rule. This test walks us through a scenario where a device powers off during Wear's
+     * Touch Lock mode.
+     */
+    @Test
+    public void stemKeyRuleIsAddedEvenWhenBehaviorsRemoved() {
+        // deactivate stem button presses
+        overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS,
+                PhoneWindowManager.SHORT_PRESS_PRIMARY_NOTHING);
+        overrideBehavior(STEM_PRIMARY_BUTTON_DOUBLE_PRESS,
+                PhoneWindowManager.DOUBLE_PRESS_PRIMARY_NOTHING);
+        overrideBehavior(STEM_PRIMARY_BUTTON_TRIPLE_PRESS,
+                PhoneWindowManager.TRIPLE_PRESS_PRIMARY_NOTHING);
+        overrideBehavior(STEM_PRIMARY_BUTTON_LONG_PRESS,
+                PhoneWindowManager.LONG_PRESS_PRIMARY_NOTHING);
+
+        // pretend like we have stem keys enabled in the xmls
+        overrideResource(
+                com.android.internal.R.integer.config_shortPressOnStemPrimaryBehavior,
+                SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
+
+        // start the PhoneWindowManager, just like would happen with a reboot
+        setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
+        // Set the stem behavior back to something normal after boot
+        overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS,
+                SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
+        // manually trigger the SettingsObserver's onChange() method because subclasses of
+        // ShortcutKeyTestBase cannot automatically pick up Settings changes.
+        triggerSettingsObserverChange();
+
+        // These calls are required to make the All Apps view show up
+        mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false);
+        mPhoneWindowManager.overrideStartActivity();
+        mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
+        mPhoneWindowManager.overrideIsUserSetupComplete(true);
+
+        sendKey(KEYCODE_STEM_PRIMARY);
+
+        // Because the rule was loaded and we changed the behavior back to non-zero, PWM should
+        // actually perform this action. It would not perform the action if the rule was missing.
+        mPhoneWindowManager.assertOpenAllAppView();
+    }
+
+    /**
+     * Ensure the stem rule is not added when stem behavior is not defined in the xml.
+     *
+     * This is the opposite of the test above.
+     */
+    @Test
+    public void stemKeyRuleIsNotAddedWhenXmlDoesntDefineIt() {
+        // deactivate stem button presses
+        overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS,
+                PhoneWindowManager.SHORT_PRESS_PRIMARY_NOTHING);
+        overrideBehavior(STEM_PRIMARY_BUTTON_DOUBLE_PRESS,
+                PhoneWindowManager.DOUBLE_PRESS_PRIMARY_NOTHING);
+        overrideBehavior(STEM_PRIMARY_BUTTON_TRIPLE_PRESS,
+                PhoneWindowManager.TRIPLE_PRESS_PRIMARY_NOTHING);
+        overrideBehavior(STEM_PRIMARY_BUTTON_LONG_PRESS,
+                PhoneWindowManager.LONG_PRESS_PRIMARY_NOTHING);
+
+        // pretend like we do not have stem keys enabled in the xmls
+        overrideResource(
+                com.android.internal.R.integer.config_shortPressOnStemPrimaryBehavior,
+                PhoneWindowManager.SHORT_PRESS_PRIMARY_NOTHING);
+        overrideResource(
+                com.android.internal.R.integer.config_longPressOnStemPrimaryBehavior,
+                PhoneWindowManager.LONG_PRESS_PRIMARY_NOTHING);
+
+        // start the PhoneWindowManager, just like would happen with a reboot
+        setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
+        // Set the stem behavior back to something normal after boot
+        // (Despite this fact, a stem press shouldn't have any behavior because there's no rule.)
+        overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS,
+                SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
+        // manually trigger the SettingsObserver's onChange() method because subclasses of
+        // ShortcutKeyTestBase cannot automatically pick up Settings changes.
+        triggerSettingsObserverChange();
+
+        // These calls are required to make the All Apps view show up
+        mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false);
+        mPhoneWindowManager.overrideStartActivity();
+        mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
+        mPhoneWindowManager.overrideIsUserSetupComplete(true);
+
+        sendKey(KEYCODE_STEM_PRIMARY);
+
+        // Because the rule was not loaded, PWM should not actually perform this action, even
+        // though the Settings override is set to non-null.
+        mPhoneWindowManager.assertNotOpenAllAppView();
+    }
+
     private void overrideBehavior(String key, int expectedBehavior) {
         Settings.Global.putLong(mContext.getContentResolver(), key, expectedBehavior);
     }
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 0776c51..52df010 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -66,6 +66,7 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.database.ContentObserver;
 import android.hardware.SensorPrivacyManager;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
@@ -246,6 +247,13 @@
         }
     }
 
+    /**
+     * {@link TestPhoneWindowManager}'s constructor.
+     *
+     * @param context The {@Context} to be used in any Context-related actions.
+     * @param supportSettingsUpdate {@code true} if this object should read and listen to provider
+     *      settings values.
+     */
     TestPhoneWindowManager(Context context, boolean supportSettingsUpdate) {
         MockitoAnnotations.initMocks(this);
         mHandler = new Handler(mTestLooper.getLooper());
@@ -416,6 +424,13 @@
         mPhoneWindowManager.dispatchUnhandledKey(mInputToken, event, FLAG_INTERACTIVE);
     }
 
+    /**
+     * Provide access to the SettingsObserver so that tests can manually trigger Settings changes.
+     */
+    ContentObserver getSettingsObserver() {
+        return mPhoneWindowManager.mSettingsObserver;
+    }
+
     long getCurrentTime() {
         return mClock.now();
     }
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 10cfb5f..3bd6496 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -623,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.
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/tests/wmtests/src/com/android/server/wm/utils/OptPropFactoryTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/OptPropFactoryTest.java
new file mode 100644
index 0000000..004de1f
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/OptPropFactoryTest.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.utils;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.PackageManager;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.wm.utils.OptPropFactory.OptProp;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.util.function.BooleanSupplier;
+
+/**
+ * Build/Install/Run:
+ * atest WmTests:OptPropFactoryTest
+ */
+@SmallTest
+@Presubmit
+public class OptPropFactoryTest {
+
+    private PackageManager mPackageManager;
+    private OptPropFactory mOptPropFactory;
+
+    @Before
+    public void setUp() {
+        mPackageManager = mock(PackageManager.class);
+        mOptPropFactory = new OptPropFactory(mPackageManager, "");
+    }
+
+    @Test
+    public void optProp_laziness() throws PackageManager.NameNotFoundException {
+        initPropAs(/* propertyValue */ true);
+        // When OptPropBuilder is created the PackageManager is not used
+        verify(mPackageManager, never()).getProperty(anyString(), anyString());
+
+        // Accessing the value multiple times only uses PackageManager once
+        final OptProp optProp = createOptProp();
+        optProp.isTrue();
+        optProp.isFalse();
+
+        verify(mPackageManager).getProperty(anyString(), anyString());
+    }
+
+    @Test
+    public void optProp_withSetValueTrue() throws PackageManager.NameNotFoundException {
+        initPropAs(/* propertyValue */ true);
+
+        final OptProp optProp = createOptProp();
+
+        assertTrue(optProp.isTrue());
+        assertFalse(optProp.isFalse());
+    }
+
+    @Test
+    public void optProp_withSetValueFalse() throws PackageManager.NameNotFoundException {
+        initPropAs(/* propertyValue */ false);
+
+        final OptProp optProp = createOptProp();
+
+        assertFalse(optProp.isTrue());
+        assertTrue(optProp.isFalse());
+    }
+
+    @Test
+    public void optProp_withSetValueWithConditionFalse()
+            throws PackageManager.NameNotFoundException {
+        initPropAs(/* propertyValue */ true);
+
+        final OptProp optProp = createOptProp(() -> false);
+
+        assertFalse(optProp.isTrue());
+        assertFalse(optProp.isFalse());
+    }
+
+    @Test
+    public void optProp_withUnsetValue() {
+        final OptProp optProp = createOptProp();
+
+        assertFalse(optProp.isTrue());
+        assertFalse(optProp.isFalse());
+    }
+
+    @Test
+    public void optProp_isUnsetWhenPropertyIsNotPresent()
+            throws PackageManager.NameNotFoundException {
+        initPropAsWithException();
+        // Property is unset
+        final OptProp optUnset = createOptProp();
+        assertFalse(optUnset.isTrue());
+        assertFalse(optUnset.isFalse());
+    }
+
+    @Test
+    public void optProp_shouldEnableWithOverrideAndProperty()
+            throws PackageManager.NameNotFoundException {
+        // Property is unset
+        final OptProp optUnset = createOptProp(() -> false);
+        assertFalse(optUnset.shouldEnableWithOverrideAndProperty(/* override */ true));
+
+        // The value is the override one
+        final OptProp optUnsetOn = createOptProp();
+        assertTrue(optUnsetOn.shouldEnableWithOverrideAndProperty(/* override */ true));
+        assertFalse(optUnsetOn.shouldEnableWithOverrideAndProperty(/* override */ false));
+
+        // Property is set to true
+        initPropAs(true);
+        final OptProp optTrue = createOptProp(() -> false);
+        assertFalse(optTrue.shouldEnableWithOverrideAndProperty(/* override */ true));
+
+        final OptProp optTrueOn = createOptProp(() -> true);
+        assertTrue(optTrueOn.shouldEnableWithOverrideAndProperty(/* override */ true));
+        assertTrue(optTrueOn.shouldEnableWithOverrideAndProperty(/* override */ false));
+
+        // Property is set to false
+        initPropAs(false);
+        final OptProp optFalse = createOptProp(() -> false);
+        assertFalse(optFalse.shouldEnableWithOverrideAndProperty(/* override */ true));
+
+        final OptProp optFalseOn = createOptProp();
+        assertFalse(optFalseOn.shouldEnableWithOverrideAndProperty(/* override */ true));
+        assertFalse(optFalseOn.shouldEnableWithOverrideAndProperty(/* override */ false));
+    }
+
+    @Test
+    public void optProp_shouldEnableWithOptInOverrideAndOptOutProperty()
+            throws PackageManager.NameNotFoundException {
+        // Property is unset
+        final OptProp optUnset = createOptProp(() -> false);
+        assertFalse(optUnset.shouldEnableWithOptInOverrideAndOptOutProperty(/* override */ true));
+
+        final OptProp optUnsetOn = createOptProp();
+        assertTrue(optUnsetOn.shouldEnableWithOptInOverrideAndOptOutProperty(/* override */ true));
+        assertFalse(
+                optUnsetOn.shouldEnableWithOptInOverrideAndOptOutProperty(/* override */ false));
+
+        // Property is set to true
+        initPropAs(true);
+        final OptProp optTrue = createOptProp(() -> false);
+        assertFalse(optTrue.shouldEnableWithOptInOverrideAndOptOutProperty(/* override */ true));
+
+        // Is the value of the override
+        final OptProp optTrueOn = createOptProp(() -> true);
+        assertTrue(optTrueOn.shouldEnableWithOptInOverrideAndOptOutProperty(/* override */ true));
+        assertFalse(optTrueOn.shouldEnableWithOptInOverrideAndOptOutProperty(/* override */ false));
+
+        // Property is set to false
+        initPropAs(false);
+        final OptProp optFalse = createOptProp(() -> false);
+        assertFalse(optFalse.shouldEnableWithOptInOverrideAndOptOutProperty(/* override */ true));
+
+        // Always false ahatever is the value of the override
+        final OptProp optFalseOn = createOptProp();
+        assertFalse(optFalseOn.shouldEnableWithOptInOverrideAndOptOutProperty(/* override */ true));
+        assertFalse(
+                optFalseOn.shouldEnableWithOptInOverrideAndOptOutProperty(/* override */ false));
+    }
+
+    @Test
+    public void optProp_shouldEnableWithOptOutOverrideAndProperty()
+            throws PackageManager.NameNotFoundException {
+        // Property is unset
+        final OptProp optUnset = createOptProp(() -> false);
+        assertFalse(optUnset.shouldEnableWithOptOutOverrideAndProperty(/* override */ true));
+
+        // Is the negate of the override value
+        final OptProp optUnsetOn = createOptProp();
+        assertTrue(optUnsetOn.shouldEnableWithOptOutOverrideAndProperty(/* override */ false));
+        assertFalse(optUnsetOn.shouldEnableWithOptOutOverrideAndProperty(/* override */ true));
+
+        // Property is set to true
+        initPropAs(true);
+        final OptProp optTrue = createOptProp(() -> false);
+        assertFalse(optTrue.shouldEnableWithOptOutOverrideAndProperty(/* override */ true));
+
+        // Is the negate of the override value
+        final OptProp optTrueOn = createOptProp(() -> true);
+        assertTrue(optTrueOn.shouldEnableWithOptOutOverrideAndProperty(/* override */ false));
+        assertFalse(optTrueOn.shouldEnableWithOptOutOverrideAndProperty(/* override */ true));
+
+        // Property is set to false
+        initPropAs(false);
+        final OptProp optFalse = createOptProp(() -> false);
+        assertFalse(optFalse.shouldEnableWithOptOutOverrideAndProperty(/* override */ true));
+
+        // Always false ahatever is the value of the override
+        final OptProp optFalseOn = createOptProp();
+        assertFalse(optFalseOn.shouldEnableWithOptOutOverrideAndProperty(/* override */ true));
+        assertFalse(optFalseOn.shouldEnableWithOptOutOverrideAndProperty(/* override */ false));
+    }
+
+    @Test
+    public void optProp_gateConditionIsInvokedOnlyOncePerInvocation()
+            throws PackageManager.NameNotFoundException {
+
+        final FakeGateCondition trueCondition = new FakeGateCondition(/* returnValue */ true);
+        final OptProp optProp = createOptProp(trueCondition);
+
+        optProp.shouldEnableWithOverrideAndProperty(/* override value */ true);
+        assertEquals(1, trueCondition.getInvocationCount());
+        trueCondition.clearInvocationCount();
+
+        initPropAs(true);
+        optProp.shouldEnableWithOptInOverrideAndOptOutProperty(/* override value */ true);
+        assertEquals(1, trueCondition.getInvocationCount());
+        trueCondition.clearInvocationCount();
+
+        optProp.shouldEnableWithOptOutOverrideAndProperty(/* override value */ true);
+        assertEquals(1, trueCondition.getInvocationCount());
+        trueCondition.clearInvocationCount();
+    }
+
+    private void initPropAs(boolean propertyValue) throws PackageManager.NameNotFoundException {
+        Mockito.clearInvocations(mPackageManager);
+        final PackageManager.Property prop = new PackageManager.Property(
+                "", /* value */ propertyValue, "", "");
+        when(mPackageManager.getProperty(anyString(), anyString())).thenReturn(prop);
+    }
+
+    private void initPropAsWithException() throws PackageManager.NameNotFoundException {
+        Mockito.clearInvocations(mPackageManager);
+        when(mPackageManager.getProperty("", "")).thenThrow(
+                new PackageManager.NameNotFoundException());
+    }
+
+    private OptProp createOptProp() {
+        return mOptPropFactory.create("");
+    }
+
+    private OptProp createOptProp(BooleanSupplier condition) {
+        return mOptPropFactory.create("", condition);
+    }
+
+    private static class FakeGateCondition implements BooleanSupplier {
+
+        private int mInvocationCount = 0;
+        private final boolean mReturnValue;
+
+        private FakeGateCondition(boolean returnValue) {
+            mReturnValue = returnValue;
+        }
+
+        @Override
+        public boolean getAsBoolean() {
+            mInvocationCount++;
+            return mReturnValue;
+        }
+
+        int getInvocationCount() {
+            return mInvocationCount;
+        }
+
+        void clearInvocationCount() {
+            mInvocationCount = 0;
+        }
+
+    }
+}
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/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
index 2ff21ad..40537c8 100644
--- a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
@@ -978,8 +978,14 @@
 
         shouldRestrictOverlayActivities = filteredAppProcessInfos.stream().anyMatch(pkg -> {
             try {
-                return mPackageManager.getProperty(PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES, pkg)
-                        .getBoolean();
+                boolean restrictUsbOverlayActivitiesForPackage = mPackageManager
+                        .getProperty(PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES, pkg).getBoolean();
+
+                if (restrictUsbOverlayActivitiesForPackage) {
+                    Slog.d(TAG, "restricting usb overlay activities as package " + pkg
+                            + " is in foreground");
+                }
+                return restrictUsbOverlayActivitiesForPackage;
             } catch (NameNotFoundException e) {
                 if (DEBUG) {
                     Slog.d(TAG, "property PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES "
@@ -989,8 +995,8 @@
             }
         });
 
-        if (shouldRestrictOverlayActivities) {
-            Slog.d(TAG, "restricting starting of usb overlay activities");
+        if (!shouldRestrictOverlayActivities) {
+            Slog.d(TAG, "starting of usb overlay activities");
         }
         return shouldRestrictOverlayActivities;
     }
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/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index e3ce766..ebabbf9 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -512,6 +512,7 @@
             EUICC_ACTIVATION_TYPE_BACKUP,
             EUICC_ACTIVATION_TYPE_TRANSFER,
             EUICC_ACTIVATION_TYPE_ACCOUNT_REQUIRED,
+            EUICC_ACTIVATION_TYPE_TRANSFER_FINAL_HOLD,
     })
     public @interface EuiccActivationType{}
 
@@ -555,6 +556,15 @@
     public static final int EUICC_ACTIVATION_TYPE_ACCOUNT_REQUIRED = 4;
 
     /**
+     * The activation flow of eSIM transfer to block the transfer process before B&R flow.
+     * This is needed to avoid connection overlapping between eSIM connection B&R connection.
+     *
+     * @hide
+     */
+     // TODO(b/329212614): add system api annotation during the allowed api timeline.
+     public static final int EUICC_ACTIVATION_TYPE_TRANSFER_FINAL_HOLD = 5;
+
+    /**
      * Euicc OTA update status which can be got by {@link #getOtaStatus}
      * @removed mistakenly exposed as system-api previously
      */
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/tools/aapt2/optimize/VersionCollapser.cpp b/tools/aapt2/optimize/VersionCollapser.cpp
index cd791bd..27fff9a 100644
--- a/tools/aapt2/optimize/VersionCollapser.cpp
+++ b/tools/aapt2/optimize/VersionCollapser.cpp
@@ -70,7 +70,7 @@
  * exception is when there is no exact matching resource for the minSdk. The next smallest one will
  * be kept.
  */
-static void CollapseVersions(int min_sdk, ResourceEntry* entry) {
+static void CollapseVersions(IAaptContext* context, int min_sdk, ResourceEntry* entry) {
   // First look for all sdks less than minSdk.
   for (auto iter = entry->values.rbegin(); iter != entry->values.rend();
        ++iter) {
@@ -102,7 +102,14 @@
       auto filter_iter =
           make_filter_iterator(iter + 1, entry->values.rend(), pred);
       while (filter_iter.HasNext()) {
-        filter_iter.Next() = {};
+        auto& next = filter_iter.Next();
+        if (context->IsVerbose()) {
+          context->GetDiagnostics()->Note(android::DiagMessage()
+                                          << "removing configuration " << next->config.to_string()
+                                          << " for entry: " << entry->name
+                                          << ", because its SDK version is smaller than minSdk");
+        }
+        next = {};
       }
     }
   }
@@ -126,6 +133,12 @@
           util::make_unique<ResourceConfigValue>(
               config_value->config.CopyWithoutSdkVersion(),
               config_value->product);
+      if (context->IsVerbose()) {
+        context->GetDiagnostics()->Note(android::DiagMessage()
+                                        << "overriding resource: " << entry->name
+                                        << ", removing SDK version from configuration "
+                                        << config_value->config.to_string());
+      }
       new_value->value = std::move(config_value->value);
       config_value = std::move(new_value);
 
@@ -147,10 +160,14 @@
 bool VersionCollapser::Consume(IAaptContext* context, ResourceTable* table) {
   TRACE_NAME("VersionCollapser::Consume");
   const int min_sdk = context->GetMinSdkVersion();
+  if (context->IsVerbose()) {
+    context->GetDiagnostics()->Note(android::DiagMessage()
+                                    << "Running VersionCollapser with minSdk = " << min_sdk);
+  }
   for (auto& package : table->packages) {
     for (auto& type : package->types) {
       for (auto& entry : type->entries) {
-        CollapseVersions(min_sdk, entry.get());
+        CollapseVersions(context, min_sdk, entry.get());
       }
     }
   }