Merge "Fix NullPointerException when fetching uuid from BluetoothDevice" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index f5bf437..beb11fc 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -64,7 +64,9 @@
     ":com.android.input.flags-aconfig-java{.generated_srcjars}",
     ":com.android.internal.foldables.flags-aconfig-java{.generated_srcjars}",
     ":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}",
+    ":com.android.media.flags.editing-aconfig-java{.generated_srcjars}",
     ":com.android.net.flags-aconfig-java{.generated_srcjars}",
+    ":com.android.net.thread.flags-aconfig-java{.generated_srcjars}",
     ":com.android.server.flags.services-aconfig-java{.generated_srcjars}",
     ":com.android.text.flags-aconfig-java{.generated_srcjars}",
     ":com.android.window.flags.window-aconfig-java{.generated_srcjars}",
@@ -133,6 +135,7 @@
         "com.android.input.flags-aconfig",
         "com.android.media.flags.bettertogether-aconfig",
         "com.android.net.flags-aconfig",
+        "com.android.net.thread.flags-aconfig",
         "com.android.server.flags.services-aconfig",
         "com.android.text.flags-aconfig",
         "com.android.window.flags.window-aconfig",
@@ -536,6 +539,21 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+// Media Editing
+aconfig_declarations {
+    name: "com.android.media.flags.editing-aconfig",
+    package: "com.android.media.editing.flags",
+    srcs: [
+        "media/java/android/media/flags/editing.aconfig",
+    ],
+}
+
+java_aconfig_library {
+    name: "com.android.media.flags.editing-aconfig-java",
+    aconfig_declarations: "com.android.media.flags.editing-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Media TV
 aconfig_declarations {
     name: "android.media.tv.flags-aconfig",
@@ -760,12 +778,25 @@
     srcs: ["core/java/android/net/flags.aconfig"],
 }
 
+// Thread network
+aconfig_declarations {
+    name: "com.android.net.thread.flags-aconfig",
+    package: "com.android.net.thread.flags",
+    srcs: ["core/java/android/net/thread/flags.aconfig"],
+}
+
 java_aconfig_library {
     name: "com.android.net.flags-aconfig-java",
     aconfig_declarations: "com.android.net.flags-aconfig",
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+java_aconfig_library {
+    name: "com.android.net.thread.flags-aconfig-java",
+    aconfig_declarations: "com.android.net.thread.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Media
 aconfig_declarations {
     name: "android.media.playback.flags-aconfig",
diff --git a/apex/jobscheduler/framework/aconfig/job.aconfig b/apex/jobscheduler/framework/aconfig/job.aconfig
index e73b434..788e824 100644
--- a/apex/jobscheduler/framework/aconfig/job.aconfig
+++ b/apex/jobscheduler/framework/aconfig/job.aconfig
@@ -13,3 +13,10 @@
     description: "Add APIs to let apps attach debug information to jobs"
     bug: "293491637"
 }
+
+flag {
+    name: "backup_jobs_exemption"
+    namespace: "backstage_power"
+    description: "Introduce a new RUN_BACKUP_JOBS permission and exemption logic allowing for longer running jobs for apps whose primary purpose is to backup or sync content."
+    bug: "318731461"
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 31214cb..696c317 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -3919,6 +3919,7 @@
                     if (locationManager != null
                             && locationManager.getProvider(LocationManager.FUSED_PROVIDER)
                                     != null) {
+                        mHasFusedLocation = true;
                         locationManager.requestLocationUpdates(LocationManager.FUSED_PROVIDER,
                                 mLocationRequest,
                                 AppSchedulingModuleThread.getExecutor(),
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 7a92cca..7f5bb5c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -5448,6 +5448,9 @@
             pw.print(Flags.FLAG_THROW_ON_UNSUPPORTED_BIAS_USAGE,
                     Flags.throwOnUnsupportedBiasUsage());
             pw.println();
+            pw.print(android.app.job.Flags.FLAG_BACKUP_JOBS_EXEMPTION,
+                    android.app.job.Flags.backupJobsExemption());
+            pw.println();
             pw.decreaseIndent();
             pw.println();
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
index 6f2393a..0cf6a7a 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -356,6 +356,9 @@
             case com.android.server.job.Flags.FLAG_THROW_ON_UNSUPPORTED_BIAS_USAGE:
                 pw.println(com.android.server.job.Flags.throwOnUnsupportedBiasUsage());
                 break;
+            case android.app.job.Flags.FLAG_BACKUP_JOBS_EXEMPTION:
+                pw.println(android.app.job.Flags.backupJobsExemption());
+                break;
             default:
                 pw.println("Unknown flag: " + flagName);
                 break;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index 14cce19..6883d18 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -972,6 +972,20 @@
             synchronized (mLock) {
                 final long earliest = getLifeCycleBeginningElapsedLocked(js);
                 final long latest = getLifeCycleEndElapsedLocked(js, nowElapsed, earliest);
+                if (latest <= earliest) {
+                    // Something has gone horribly wrong. This has only occurred on incorrectly
+                    // configured tests, but add a check here for safety.
+                    Slog.wtf(TAG, "Got invalid latest when scheduling alarm."
+                            + " Prefetch=" + js.getJob().isPrefetch());
+                    // Since things have gone wrong, the safest and most reliable thing to do is
+                    // stop applying flex policy to the job.
+                    mFlexibilityTracker.setNumDroppedFlexibleConstraints(js,
+                            js.getNumAppliedFlexibleConstraints());
+                    mJobsToCheck.add(js);
+                    mHandler.sendEmptyMessage(MSG_CHECK_JOBS);
+                    return;
+                }
+
                 final long nextTimeElapsed =
                         getNextConstraintDropTimeElapsedLocked(js, earliest, latest);
 
diff --git a/core/api/Android.bp b/core/api/Android.bp
index 907916a..8d8a82b 100644
--- a/core/api/Android.bp
+++ b/core/api/Android.bp
@@ -96,21 +96,3 @@
     name: "non-updatable-test-lint-baseline.txt",
     srcs: ["test-lint-baseline.txt"],
 }
-
-java_api_contribution {
-    name: "api-stubs-docs-non-updatable-public-stubs",
-    api_surface: "public",
-    api_file: "current.txt",
-    visibility: [
-        "//build/orchestrator/apis",
-    ],
-}
-
-java_api_contribution {
-    name: "frameworks-base-core-api-module-lib-stubs",
-    api_surface: "module-lib",
-    api_file: "module-lib-current.txt",
-    visibility: [
-        "//build/orchestrator/apis",
-    ],
-}
diff --git a/core/api/current.txt b/core/api/current.txt
index 3acdb32..9e3919d 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -277,6 +277,7 @@
     field @FlaggedApi("android.companion.flags.device_presence") public static final String REQUEST_OBSERVE_DEVICE_UUID_PRESENCE = "android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE";
     field public static final String REQUEST_PASSWORD_COMPLEXITY = "android.permission.REQUEST_PASSWORD_COMPLEXITY";
     field @Deprecated public static final String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
+    field @FlaggedApi("android.app.job.backup_jobs_exemption") public static final String RUN_BACKUP_JOBS = "android.permission.RUN_BACKUP_JOBS";
     field public static final String RUN_USER_INITIATED_JOBS = "android.permission.RUN_USER_INITIATED_JOBS";
     field public static final String SCHEDULE_EXACT_ALARM = "android.permission.SCHEDULE_EXACT_ALARM";
     field public static final String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE";
@@ -284,6 +285,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 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";
@@ -18722,8 +18724,8 @@
     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.permission.MANAGE_BIOMETRIC_DIALOG") public android.graphics.Bitmap getLogoBitmap();
-    method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @DrawableRes @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public int getLogoRes();
+    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") @DrawableRes @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public int getLogoRes();
     method @Nullable public CharSequence getNegativeButtonText();
     method @Nullable public CharSequence getSubtitle();
     method @NonNull public CharSequence getTitle();
@@ -18773,8 +18775,8 @@
     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.permission.MANAGE_BIOMETRIC_DIALOG") public android.hardware.biometrics.BiometricPrompt.Builder setLogo(@DrawableRes int);
-    method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public android.hardware.biometrics.BiometricPrompt.Builder setLogo(@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 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 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);
@@ -18800,14 +18802,14 @@
   }
 
   @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptContentItemBulletedText implements android.os.Parcelable android.hardware.biometrics.PromptContentItem {
-    ctor public PromptContentItemBulletedText(@NonNull CharSequence);
+    ctor public PromptContentItemBulletedText(@NonNull String);
     method public int describeContents();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.biometrics.PromptContentItemBulletedText> CREATOR;
   }
 
   @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptContentItemPlainText implements android.os.Parcelable android.hardware.biometrics.PromptContentItem {
-    ctor public PromptContentItemPlainText(@NonNull CharSequence);
+    ctor public PromptContentItemPlainText(@NonNull String);
     method public int describeContents();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.biometrics.PromptContentItemPlainText> CREATOR;
@@ -18818,7 +18820,7 @@
 
   @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 CharSequence getDescription();
+    method @Nullable public String getDescription();
     method @NonNull public java.util.List<android.hardware.biometrics.PromptContentItem> getListItems();
     method public static int getMaxEachItemCharacterNumber();
     method public static int getMaxItemCount();
@@ -18831,7 +18833,7 @@
     method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder addListItem(@NonNull android.hardware.biometrics.PromptContentItem);
     method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder addListItem(@NonNull android.hardware.biometrics.PromptContentItem, int);
     method @NonNull public android.hardware.biometrics.PromptVerticalListContentView build();
-    method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder setDescription(@NonNull CharSequence);
+    method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder setDescription(@NonNull String);
   }
 
 }
@@ -24299,7 +24301,7 @@
     method @Nullable public android.media.MediaRouter2.RoutingController getController(@NonNull String);
     method @NonNull public java.util.List<android.media.MediaRouter2.RoutingController> getControllers();
     method @NonNull public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context);
-    method @FlaggedApi("com.android.media.flags.enable_cross_user_routing_in_media_router2") @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MEDIA_CONTENT_CONTROL, android.Manifest.permission.MEDIA_ROUTING_CONTROL}) public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context, @NonNull String, @NonNull android.os.UserHandle);
+    method @FlaggedApi("com.android.media.flags.enable_cross_user_routing_in_media_router2") @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MEDIA_CONTENT_CONTROL, android.Manifest.permission.MEDIA_ROUTING_CONTROL}) public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context, @NonNull String);
     method @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") @Nullable public android.media.RouteListingPreference getRouteListingPreference();
     method @NonNull public java.util.List<android.media.MediaRoute2Info> getRoutes();
     method @NonNull public android.media.MediaRouter2.RoutingController getSystemController();
@@ -25715,9 +25717,48 @@
     field public static final String KEY_STATSD_ATOM = "bundlesession-statsd-atom";
   }
 
+  @FlaggedApi("com.android.media.editing.flags.add_media_metrics_editing") public final class EditingEndedEvent extends android.media.metrics.Event implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getErrorCode();
+    method public int getFinalState();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.metrics.EditingEndedEvent> CREATOR;
+    field public static final int ERROR_CODE_AUDIO_PROCESSING_FAILED = 18; // 0x12
+    field public static final int ERROR_CODE_DECODER_INIT_FAILED = 11; // 0xb
+    field public static final int ERROR_CODE_DECODING_FAILED = 12; // 0xc
+    field public static final int ERROR_CODE_DECODING_FORMAT_UNSUPPORTED = 13; // 0xd
+    field public static final int ERROR_CODE_ENCODER_INIT_FAILED = 14; // 0xe
+    field public static final int ERROR_CODE_ENCODING_FAILED = 15; // 0xf
+    field public static final int ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED = 16; // 0x10
+    field public static final int ERROR_CODE_FAILED_RUNTIME_CHECK = 2; // 0x2
+    field public static final int ERROR_CODE_IO_BAD_HTTP_STATUS = 6; // 0x6
+    field public static final int ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED = 9; // 0x9
+    field public static final int ERROR_CODE_IO_FILE_NOT_FOUND = 7; // 0x7
+    field public static final int ERROR_CODE_IO_NETWORK_CONNECTION_FAILED = 4; // 0x4
+    field public static final int ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT = 5; // 0x5
+    field public static final int ERROR_CODE_IO_NO_PERMISSION = 8; // 0x8
+    field public static final int ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE = 10; // 0xa
+    field public static final int ERROR_CODE_IO_UNSPECIFIED = 3; // 0x3
+    field public static final int ERROR_CODE_MUXING_FAILED = 19; // 0x13
+    field public static final int ERROR_CODE_NONE = 1; // 0x1
+    field public static final int ERROR_CODE_VIDEO_FRAME_PROCESSING_FAILED = 17; // 0x11
+    field public static final int FINAL_STATE_CANCELED = 2; // 0x2
+    field public static final int FINAL_STATE_ERROR = 3; // 0x3
+    field public static final int FINAL_STATE_SUCCEEDED = 1; // 0x1
+  }
+
+  @FlaggedApi("com.android.media.editing.flags.add_media_metrics_editing") public static final class EditingEndedEvent.Builder {
+    ctor public EditingEndedEvent.Builder(int);
+    method @NonNull public android.media.metrics.EditingEndedEvent build();
+    method @NonNull public android.media.metrics.EditingEndedEvent.Builder setErrorCode(int);
+    method @NonNull public android.media.metrics.EditingEndedEvent.Builder setMetricsBundle(@NonNull android.os.Bundle);
+    method @NonNull public android.media.metrics.EditingEndedEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=0xffffffff) long);
+  }
+
   public final class EditingSession implements java.lang.AutoCloseable {
     method public void close();
     method @NonNull public android.media.metrics.LogSessionId getSessionId();
+    method @FlaggedApi("com.android.media.editing.flags.add_media_metrics_editing") public void reportEditingEndedEvent(@NonNull android.media.metrics.EditingEndedEvent);
   }
 
   public abstract class Event {
@@ -40575,7 +40616,7 @@
     method public int getPriorityCategoryReminders();
     method public int getPriorityCategoryRepeatCallers();
     method public int getPriorityCategorySystem();
-    method @FlaggedApi("android.app.modes_api") public int getPriorityChannels();
+    method @FlaggedApi("android.app.modes_api") public int getPriorityChannelsAllowed();
     method public int getPriorityConversationSenders();
     method public int getPriorityMessageSenders();
     method public int getVisualEffectAmbient();
@@ -41703,10 +41744,10 @@
     method public void disconnect(@NonNull android.telecom.DisconnectCause, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
     method @NonNull public android.os.ParcelUuid getCallId();
     method public void requestCallEndpointChange(@NonNull android.telecom.CallEndpoint, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
+    method @FlaggedApi("com.android.server.telecom.flags.set_mute_state") public void requestMuteState(boolean, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
     method public void sendEvent(@NonNull String, @NonNull android.os.Bundle);
     method public void setActive(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
     method public void setInactive(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
-    method @FlaggedApi("com.android.server.telecom.flags.set_mute_state") public void setMuteState(boolean, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
     method public void startCallStreaming(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
   }
 
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 0b17e03..ea008ac 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -22,7 +22,7 @@
     field public static final String ACCESS_RCS_USER_CAPABILITY_EXCHANGE = "android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE";
     field public static final String ACCESS_SHARED_LIBRARIES = "android.permission.ACCESS_SHARED_LIBRARIES";
     field public static final String ACCESS_SHORTCUTS = "android.permission.ACCESS_SHORTCUTS";
-    field public static final String ACCESS_SMARTSPACE = "android.permission.ACCESS_SMARTSPACE";
+    field @FlaggedApi("android.app.smartspace.flags.access_smartspace") public static final String ACCESS_SMARTSPACE = "android.permission.ACCESS_SMARTSPACE";
     field public static final String ACCESS_SURFACE_FLINGER = "android.permission.ACCESS_SURFACE_FLINGER";
     field public static final String ACCESS_TUNED_INFO = "android.permission.ACCESS_TUNED_INFO";
     field public static final String ACCESS_TV_DESCRAMBLER = "android.permission.ACCESS_TV_DESCRAMBLER";
@@ -871,6 +871,10 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.PackageOps> CREATOR;
   }
 
+  @FlaggedApi("android.app.bic_client") public final class BackgroundInstallControlManager {
+    method @FlaggedApi("android.app.bic_client") @NonNull @RequiresPermission(android.Manifest.permission.GET_BACKGROUND_INSTALLED_PACKAGES) public java.util.List<android.content.pm.PackageInfo> getBackgroundInstalledPackages(long);
+  }
+
   public class BroadcastOptions {
     method public void clearRequireCompatChange();
     method public int getPendingIntentBackgroundActivityStartMode();
@@ -3388,11 +3392,10 @@
   }
 
   @FlaggedApi("android.companion.virtual.flags.virtual_camera") public static final class VirtualCameraConfig.Builder {
-    ctor public VirtualCameraConfig.Builder();
+    ctor public VirtualCameraConfig.Builder(@NonNull String);
     method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder addStreamConfig(@IntRange(from=1) int, @IntRange(from=1) int, int, @IntRange(from=1) int);
     method @NonNull public android.companion.virtual.camera.VirtualCameraConfig build();
     method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setLensFacing(int);
-    method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setName(@NonNull String);
     method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setSensorOrientation(int);
     method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setVirtualCameraCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.camera.VirtualCameraCallback);
   }
@@ -6886,7 +6889,6 @@
   public final class MediaRouter2 {
     method @NonNull public java.util.List<android.media.MediaRoute2Info> getAllRoutes();
     method @Nullable public String getClientPackageName();
-    method @Nullable @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void setRouteVolume(@NonNull android.media.MediaRoute2Info, int);
     method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void startScan();
     method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void stopScan();
@@ -14774,6 +14776,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean matchesCurrentSimOperator(@NonNull String, int, @Nullable String);
     method public boolean needsOtaServiceProvisioning();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyOtaEmergencyNumberDbInstalled();
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(android.Manifest.permission.DUMP) public void persistEmergencyCallDiagnosticData(@NonNull String, @NonNull android.telephony.TelephonyManager.EmergencyCallDiagnosticParams);
     method @RequiresPermission(android.Manifest.permission.REBOOT) public int prepareForUnattendedReboot();
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerCarrierPrivilegesCallback(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CarrierPrivilegesCallback);
@@ -14952,6 +14955,21 @@
     method public default void onCarrierServiceChanged(@Nullable String, int);
   }
 
+  @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final class TelephonyManager.EmergencyCallDiagnosticParams {
+    method public long getLogcatCollectionStartTimeMillis();
+    method public boolean isLogcatCollectionEnabled();
+    method public boolean isTelecomDumpSysCollectionEnabled();
+    method public boolean isTelephonyDumpSysCollectionEnabled();
+  }
+
+  public static final class TelephonyManager.EmergencyCallDiagnosticParams.Builder {
+    ctor public TelephonyManager.EmergencyCallDiagnosticParams.Builder();
+    method @NonNull public android.telephony.TelephonyManager.EmergencyCallDiagnosticParams build();
+    method @NonNull public android.telephony.TelephonyManager.EmergencyCallDiagnosticParams.Builder setLogcatCollectionStartTimeMillis(long);
+    method @NonNull public android.telephony.TelephonyManager.EmergencyCallDiagnosticParams.Builder setTelecomDumpSysCollectionEnabled(boolean);
+    method @NonNull public android.telephony.TelephonyManager.EmergencyCallDiagnosticParams.Builder setTelephonyDumpSysCollectionEnabled(boolean);
+  }
+
   public static class TelephonyManager.ModemActivityInfoException extends java.lang.Exception {
     ctor public TelephonyManager.ModemActivityInfoException(int);
     method public int getErrorCode();
@@ -17159,7 +17177,7 @@
     method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getSatelliteAttachRestrictionReasonsForCarrier(int);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingSatelliteDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatelliteService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
-    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void registerForNtnSignalStrengthChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.NtnSignalStrengthCallback) throws android.telephony.satellite.SatelliteManager.SatelliteException;
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void registerForNtnSignalStrengthChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.NtnSignalStrengthCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteCapabilitiesChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteCapabilitiesCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteModemStateCallback);
@@ -17226,6 +17244,7 @@
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_UNKNOWN = -1; // 0xffffffff
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_ACCESS_BARRED = 16; // 0x10
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_ERROR = 1; // 0x1
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_ILLEGAL_STATE = 23; // 0x17
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_ARGUMENTS = 8; // 0x8
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_MODEM_STATE = 7; // 0x7
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_TELEPHONY_STATE = 6; // 0x6
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index e288b42..1bdbd4c5 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -6792,6 +6792,7 @@
                             }
                         }
                         if (killApp) {
+                            // Keep in sync with "perhaps it was removed" case below.
                             mPackages.remove(packages[i]);
                             mResourcePackages.remove(packages[i]);
                         }
@@ -6859,6 +6860,12 @@
                                 }
                             } catch (RemoteException e) {
                             }
+                        } else {
+                            // No package, perhaps it was removed?
+                            Slog.e(TAG, "Package [" + packages[i] + "] reported as REPLACED,"
+                                    + " but missing application info. Assuming REMOVED.");
+                            mPackages.remove(packages[i]);
+                            mResourcePackages.remove(packages[i]);
                         }
                     }
                 }
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index ccd8456..00c4b0f 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1548,9 +1548,16 @@
     public static final int OP_READ_SYSTEM_GRAMMATICAL_GENDER =
             AppProtoEnums.APP_OP_READ_SYSTEM_GRAMMATICAL_GENDER;
 
+    /**
+     * Allows an app whose primary use case is to backup or sync content to run longer jobs.
+     *
+     * @hide
+     */
+    public static final int OP_RUN_BACKUP_JOBS = AppProtoEnums.APP_OP_RUN_BACKUP_JOBS;
+
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final int _NUM_OP = 144;
+    public static final int _NUM_OP = 145;
 
     /**
      * All app ops represented as strings.
@@ -1700,6 +1707,7 @@
             OPSTR_ENABLE_MOBILE_DATA_BY_USER,
             OPSTR_RESERVED_FOR_TESTING,
             OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER,
+            OPSTR_RUN_BACKUP_JOBS,
     })
     public @interface AppOpString {}
 
@@ -2392,6 +2400,13 @@
     public static final String OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER =
             "android:read_system_grammatical_gender";
 
+    /**
+     * Allows an app whose primary use case is to backup or sync content to run longer jobs.
+     *
+     * @hide
+     */
+    public static final String OPSTR_RUN_BACKUP_JOBS = "android:run_backup_jobs";
+
     /** {@link #sAppOpsToNote} not initialized yet for this op */
     private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
     /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -2504,6 +2519,7 @@
             OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
             OP_MEDIA_ROUTING_CONTROL,
             OP_READ_SYSTEM_GRAMMATICAL_GENDER,
+            OP_RUN_BACKUP_JOBS,
     };
 
     static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
@@ -2961,6 +2977,8 @@
                 // will make it an app-op permission in the future.
                 // .setPermission(Manifest.permission.READ_SYSTEM_GRAMMATICAL_GENDER)
                 .build(),
+        new AppOpInfo.Builder(OP_RUN_BACKUP_JOBS, OPSTR_RUN_BACKUP_JOBS, "RUN_BACKUP_JOBS")
+                .setPermission(Manifest.permission.RUN_BACKUP_JOBS).build(),
     };
 
     // The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/BackgroundInstallControlManager.java b/core/java/android/app/BackgroundInstallControlManager.java
new file mode 100644
index 0000000..664fceb
--- /dev/null
+++ b/core/java/android/app/BackgroundInstallControlManager.java
@@ -0,0 +1,102 @@
+/*
+ * 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 android.app;
+
+import static android.Manifest.permission.GET_BACKGROUND_INSTALLED_PACKAGES;
+import static android.annotation.SystemApi.Client.PRIVILEGED_APPS;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.content.pm.IBackgroundInstallControlService;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+import java.util.List;
+
+/**
+ * BackgroundInstallControlManager client allows apps to query apps installed in background.
+ *
+ * <p>Any applications that was installed without an accompanying installer UI activity paired
+ * with recorded user interaction event is considered background installed. This is determined by
+ * analysis of user-activity logs.
+ *
+ * <p>Warning: BackgroundInstallControl should not be considered a definitive
+ * authority of identifying background installed applications. Consumers can use this as a
+ * supplementary signal, but must perform additional due diligence to confirm the install nature
+ * of the package.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_BIC_CLIENT)
+@SystemApi(client = PRIVILEGED_APPS)
+@SystemService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE)
+public final class BackgroundInstallControlManager {
+
+    private static final String TAG = "BackgroundInstallControlManager";
+    private static IBackgroundInstallControlService sService;
+    private final Context mContext;
+
+    BackgroundInstallControlManager(Context context) {
+        mContext = context;
+    }
+
+    private static IBackgroundInstallControlService getService() {
+        if (sService == null) {
+            sService =
+                    IBackgroundInstallControlService.Stub.asInterface(
+                            ServiceManager.getService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE));
+        }
+        return sService;
+    }
+
+    /**
+     * Returns a full list of {@link PackageInfo} of apps currently installed for the current user
+     * that are considered installed in the background.
+     *
+     * <p>Refer to top level doc {@link BackgroundInstallControlManager} for more details on
+     * background-installed applications.
+     * <p>
+     *
+     * @param flags - Flags will be used to call
+     * {@link PackageManager#getInstalledPackages(PackageInfoFlags)} to retrieve installed packages.
+     * @return A list of packages retrieved from {@link PackageManager} with non-background
+     * installed app filter applied.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_BIC_CLIENT)
+    @SystemApi
+    @RequiresPermission(GET_BACKGROUND_INSTALLED_PACKAGES)
+    public @NonNull List<PackageInfo> getBackgroundInstalledPackages(
+            @PackageManager.PackageInfoFlagsBits long flags) {
+        List<PackageInfo> backgroundInstalledPackages;
+        try {
+            return getService()
+                    .getBackgroundInstalledPackages(flags, mContext.getUserId())
+                    .getList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index ed0cfbe..a81ad3c 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5487,6 +5487,15 @@
             return mColors;
         }
 
+        /**
+         * @param isHeader If the notification is a notification header
+         * @return An instance of mColors after resolving the palette
+         */
+        private Colors getColors(boolean isHeader) {
+            mColors.resolvePalette(mContext, mN.color, !isHeader && mN.isColorized(), mInNightMode);
+            return mColors;
+        }
+
         private void updateBackgroundColor(RemoteViews contentView,
                 StandardTemplateParams p) {
             if (isBackgroundColorized(p)) {
@@ -6618,6 +6627,23 @@
             return getColors(p).getContrastColor();
         }
 
+        /**
+         * Gets the foreground color of the small icon.  If the notification is colorized, this
+         * is the primary text color, otherwise it's the contrast-adjusted app-provided color.
+         * @hide
+         */
+        public @ColorInt int getSmallIconColor(boolean isHeader) {
+            return getColors(/* isHeader = */ isHeader).getContrastColor();
+        }
+
+        /**
+         * Gets the background color of the notification.
+         * @hide
+         */
+        public @ColorInt int getBackgroundColor(boolean isHeader) {
+            return getColors(/* isHeader = */ isHeader).getBackgroundColor();
+        }
+
         /** @return the theme's accent color for colored UI elements. */
         private @ColorInt int getPrimaryAccentColor(StandardTemplateParams p) {
             return getColors(p).getPrimaryAccentColor();
@@ -8532,6 +8558,8 @@
             boolean isImportantConversation = mConversationType == CONVERSATION_TYPE_IMPORTANT;
             boolean isHeaderless = !isConversationLayout && isCollapsed;
 
+            //TODO (b/217799515): ensure mConversationTitle always returns the correct
+            // conversationTitle, probably set mConversationTitle = conversationTitle after this
             CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
                     ? super.mBigContentTitle
                     : mConversationTitle;
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 0760d4d..3b5bba2 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -90,8 +90,8 @@
 per-file pinner-client.aconfig = file:/core/java/android/app/pinner/OWNERS
 
 # BackgroundInstallControlManager
-per-file BackgroundInstallControlManager.java = file:/services/core/java/com/android/server/pm/OWNERS
-per-file background_install_control_manager.aconfig = file:/services/core/java/com/android/server/pm/OWNERS
+per-file BackgroundInstallControlManager.java = file:/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS
+per-file background_install_control_manager.aconfig = file:/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS
 
 # ResourcesManager
 per-file ResourcesManager.java = file:RESOURCES_OWNERS
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
index 350cf3d..06a0f5c 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
+++ b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
@@ -196,13 +196,12 @@
      * <li>At least one stream must be added with {@link #addStreamConfig(int, int, int, int)}.
      * <li>A callback must be set with {@link #setVirtualCameraCallback(Executor,
      *     VirtualCameraCallback)}
-     * <li>A camera name must be set with {@link #setName(String)}
      * <li>A lens facing must be set with {@link #setLensFacing(int)}
      */
     @FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
     public static final class Builder {
 
-        private String mName;
+        private final String mName;
         private final ArraySet<VirtualCameraStreamConfig> mStreamConfigurations = new ArraySet<>();
         private Executor mCallbackExecutor;
         private VirtualCameraCallback mCallback;
@@ -210,12 +209,12 @@
         private int mLensFacing = LENS_FACING_UNKNOWN;
 
         /**
-         * Sets the name of the virtual camera instance.
+         * Creates a new instance of {@link Builder}.
+         *
+         * @param name The name of the {@link VirtualCamera}.
          */
-        @NonNull
-        public Builder setName(@NonNull String name) {
-            mName = requireNonNull(name, "Display name cannot be null");
-            return this;
+        public Builder(@NonNull String name) {
+            mName = requireNonNull(name, "Name cannot be null");
         }
 
         /**
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 4724e86..8744eae 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -128,6 +128,7 @@
  * <a href="/training/basics/intents/package-visibility">manage package visibility</a>.
  * </p>
  */
+@android.ravenwood.annotation.RavenwoodKeepPartialClass
 public abstract class PackageManager {
     private static final String TAG = "PackageManager";
 
@@ -5492,6 +5493,7 @@
      * application info.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public static class Flags {
         final long mValue;
         protected Flags(long value) {
@@ -5506,6 +5508,7 @@
      * Specific flags used for retrieving package info. Example:
      * {@code PackageManager.getPackageInfo(packageName, PackageInfoFlags.of(0)}
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public final static class PackageInfoFlags extends Flags {
         private PackageInfoFlags(@PackageInfoFlagsBits long value) {
             super(value);
@@ -5519,6 +5522,7 @@
     /**
      * Specific flags used for retrieving application info.
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public final static class ApplicationInfoFlags extends Flags {
         private ApplicationInfoFlags(@ApplicationInfoFlagsBits long value) {
             super(value);
@@ -5532,6 +5536,7 @@
     /**
      * Specific flags used for retrieving component info.
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public final static class ComponentInfoFlags extends Flags {
         private ComponentInfoFlags(@ComponentInfoFlagsBits long value) {
             super(value);
@@ -5545,6 +5550,7 @@
     /**
      * Specific flags used for retrieving resolve info.
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public final static class ResolveInfoFlags extends Flags {
         private ResolveInfoFlags(@ResolveInfoFlagsBits long value) {
             super(value);
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 8fd78bd..3e9f260 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -52,6 +52,7 @@
  * @hide
  */
 @TestApi
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class UserInfo implements Parcelable {
 
     /**
@@ -438,6 +439,7 @@
     /**
      * @return true if this user can be switched to.
      **/
+    @android.ravenwood.annotation.RavenwoodThrow
     public boolean supportsSwitchTo() {
         if (partial || !isEnabled()) {
             // Don't support switching to disabled or partial users, which includes users with
@@ -455,6 +457,7 @@
      * @return true if user is of type {@link UserManager#USER_TYPE_SYSTEM_HEADLESS} and
      * {@link com.android.internal.R.bool.config_canSwitchToHeadlessSystemUser} is true.
      */
+    @android.ravenwood.annotation.RavenwoodThrow
     private boolean canSwitchToHeadlessSystemUser() {
         return UserManager.USER_TYPE_SYSTEM_HEADLESS.equals(userType) && Resources.getSystem()
                 .getBoolean(com.android.internal.R.bool.config_canSwitchToHeadlessSystemUser);
@@ -465,6 +468,7 @@
      * @deprecated Use {@link UserInfo#supportsSwitchTo} instead.
      */
     @Deprecated
+    @android.ravenwood.annotation.RavenwoodThrow
     public boolean supportsSwitchToByUser() {
         return supportsSwitchTo();
     }
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index e4e9fba..fd87290 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -153,3 +153,18 @@
     bug: "291135724"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "fix_system_apps_first_install_time"
+    namespace: "package_manager_service"
+    description: "Feature flag to fix the first-install timestamps for system apps."
+    bug: "321258605"
+    is_fixed_read_only: true
+}
+
+flag {
+    name: "allow_sdk_sandbox_query_intent_activities"
+    namespace: "package_manager_service"
+    description: "Feature flag to allow the sandbox SDK to query intent activities of the client app."
+    bug: "295842134"
+}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 9644d80..c083437 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -85,6 +85,7 @@
     description: "Enable auto-locking private space on device restarts"
     bug: "296993385"
 }
+
 flag {
     name: "enable_system_user_only_for_services_and_providers"
     namespace: "multiuser"
@@ -92,3 +93,10 @@
     bug: "302354856"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "allow_private_profile_apis"
+    namespace: "profile_experiences"
+    description: "Enable only the API changes to support private space"
+    bug: "299069460"
+}
diff --git a/core/java/android/content/res/FontScaleConverterFactory.java b/core/java/android/content/res/FontScaleConverterFactory.java
index cbe4c62..625d7cb 100644
--- a/core/java/android/content/res/FontScaleConverterFactory.java
+++ b/core/java/android/content/res/FontScaleConverterFactory.java
@@ -58,6 +58,16 @@
         synchronized (LOOKUP_TABLES_WRITE_LOCK) {
             putInto(
                     sLookupTables,
+                    /* scaleKey= */ 1.1f,
+                    new FontScaleConverterImpl(
+                            /* fromSp= */
+                            new float[] {   8f,   10f,   12f,   14f,   18f,   20f,   24f,   30f,  100},
+                            /* toDp=   */
+                            new float[] { 8.8f,   11f, 13.2f, 15.6f, 19.2f, 21.2f, 24.8f,   30f,  100})
+            );
+
+            putInto(
+                    sLookupTables,
                     /* scaleKey= */ 1.15f,
                     new FontScaleConverterImpl(
                             /* fromSp= */
diff --git a/core/java/android/credentials/ui/AuthenticationEntry.java b/core/java/android/credentials/ui/AuthenticationEntry.java
index b1a382c..9bd0871 100644
--- a/core/java/android/credentials/ui/AuthenticationEntry.java
+++ b/core/java/android/credentials/ui/AuthenticationEntry.java
@@ -34,15 +34,24 @@
 /**
  * An authentication entry.
  *
+ * Applicable only for credential retrieval flow, authentication entries are a special type of
+ * entries that require the user to unlock the given provider before its credential options can
+ * be fully rendered.
+ *
  * @hide
  */
 @TestApi
 public final class AuthenticationEntry implements Parcelable {
-    @NonNull private final String mKey;
-    @NonNull private final String mSubkey;
-    @NonNull private final @Status int mStatus;
-    @Nullable private Intent mFrameworkExtrasIntent;
-    @NonNull private final Slice mSlice;
+    @NonNull
+    private final String mKey;
+    @NonNull
+    private final String mSubkey;
+    @NonNull
+    private final @Status int mStatus;
+    @Nullable
+    private Intent mFrameworkExtrasIntent;
+    @NonNull
+    private final Slice mSlice;
 
     /** @hide **/
     @IntDef(prefix = {"STATUS_"}, value = {
@@ -51,15 +60,21 @@
             STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT,
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface Status {}
+    public @interface Status {
+    }
 
     /** This entry is still locked, as initially supplied by the provider. */
     public static final int STATUS_LOCKED = 0;
-    /** This entry was unlocked but didn't contain any credential. Meanwhile, "less recent" means
-     *  there is another such entry that was unlocked more recently. */
+    /**
+     * This entry was unlocked but didn't contain any credential. Meanwhile, "less recent" means
+     * there is another such entry that was unlocked more recently.
+     */
     public static final int STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT = 1;
-    /** This is the most recent entry that was unlocked but didn't contain any credential.
-     *  There should be at most one authentication entry with this status. */
+    /**
+     * This is the most recent entry that was unlocked but didn't contain any credential.
+     *
+     * There will be at most one authentication entry with this status.
+     */
     public static final int STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT = 2;
 
     private AuthenticationEntry(@NonNull Parcel in) {
@@ -74,9 +89,11 @@
         AnnotationValidations.validate(NonNull.class, null, mSlice);
     }
 
-    /** Constructor to be used for an entry that does not require further activities
+    /**
+     * Constructor to be used for an entry that does not require further activities
      * to be invoked when selected.
      */
+    // TODO(b/322065508): remove this constructor.
     public AuthenticationEntry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice,
             @Status int status) {
         mKey = key;
@@ -95,9 +112,9 @@
     }
 
     /**
-    * Returns the identifier of this entry that's unique within the context of the CredentialManager
-    * request.
-    */
+     * Returns the identifier of this entry that's unique within the context of the
+     * CredentialManager request.
+     */
     @NonNull
     public String getKey() {
         return mKey;
@@ -111,23 +128,23 @@
         return mSubkey;
     }
 
-    /**
-    * Returns the Slice to be rendered.
-    */
+    /** Returns the Slice to be rendered. */
     @NonNull
     public Slice getSlice() {
         return mSlice;
     }
 
-    /**
-     * Returns the entry status.
-     */
+    /** Returns the entry status, depending on which the entry will be rendered differently. */
     @NonNull
     @Status
     public int getStatus() {
         return mStatus;
     }
 
+    /**
+     * Returns the framework intent to be filled in when launching this entry's provider
+     * PendingIntent.
+     */
     @Nullable
     @SuppressLint("IntentBuilderName") // Not building a new intent.
     public Intent getFrameworkExtrasIntent() {
diff --git a/core/java/android/credentials/ui/BaseDialogResult.java b/core/java/android/credentials/ui/BaseDialogResult.java
index e8cf5ab..e985a46 100644
--- a/core/java/android/credentials/ui/BaseDialogResult.java
+++ b/core/java/android/credentials/ui/BaseDialogResult.java
@@ -24,8 +24,6 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import com.android.internal.util.AnnotationValidations;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -46,7 +44,7 @@
 
     /**
      * Used for the UX to construct the {@code resultData Bundle} to send via the {@code
-     *  ResultReceiver}.
+     * ResultReceiver}.
      */
     public static void addToBundle(@NonNull BaseDialogResult result, @NonNull Bundle bundle) {
         bundle.putParcelable(EXTRA_BASE_RESULT, result);
@@ -66,13 +64,14 @@
             RESULT_CODE_DATA_PARSING_FAILURE,
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface ResultCode {}
+    public @interface ResultCode {
+    }
 
     /** User intentionally canceled the dialog. */
     public static final int RESULT_CODE_DIALOG_USER_CANCELED = 0;
     /**
-     * The user has consented to switching to a new default provider. The provider info is in the
-     * {@code resultData}.
+     * The UI was stopped since the user has chosen to navigate to the Settings UI to reconfigure
+     * their providers.
      */
     public static final int RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS = 1;
     /**
@@ -86,6 +85,7 @@
     public static final int RESULT_CODE_DATA_PARSING_FAILURE = 3;
 
     @Nullable
+    @Deprecated
     private final IBinder mRequestToken;
 
     public BaseDialogResult(@Nullable IBinder requestToken) {
@@ -94,6 +94,7 @@
 
     /** Returns the unique identifier for the request that launched the operation. */
     @Nullable
+    @Deprecated
     public IBinder getRequestToken() {
         return mRequestToken;
     }
@@ -115,14 +116,14 @@
 
     public static final @NonNull Creator<BaseDialogResult> CREATOR =
             new Creator<BaseDialogResult>() {
-        @Override
-        public BaseDialogResult createFromParcel(@NonNull Parcel in) {
-            return new BaseDialogResult(in);
-        }
+                @Override
+                public BaseDialogResult createFromParcel(@NonNull Parcel in) {
+                    return new BaseDialogResult(in);
+                }
 
-        @Override
-        public BaseDialogResult[] newArray(int size) {
-            return new BaseDialogResult[size];
-        }
-    };
+                @Override
+                public BaseDialogResult[] newArray(int size) {
+                    return new BaseDialogResult[size];
+                }
+            };
 }
diff --git a/core/java/android/credentials/ui/CancelUiRequest.java b/core/java/android/credentials/ui/CancelUiRequest.java
index d4c249e..712424ce 100644
--- a/core/java/android/credentials/ui/CancelUiRequest.java
+++ b/core/java/android/credentials/ui/CancelUiRequest.java
@@ -24,7 +24,7 @@
 import com.android.internal.util.AnnotationValidations;
 
 /**
- * A request to cancel any ongoing UI matching this request.
+ * A request to cancel the ongoing UI matching the identifier token in this request.
  *
  * @hide
  */
@@ -33,9 +33,12 @@
     /**
      * The intent extra key for the {@code CancelUiRequest} object when launching the UX
      * activities.
+     *
+     * @hide
      */
-    @NonNull public static final String EXTRA_CANCEL_UI_REQUEST =
-            "android.credentials.ui.extra.EXTRA_CANCEL_UI_REQUEST";
+    @NonNull
+    public static final String EXTRA_CANCEL_UI_REQUEST =
+            "android.credentials.ui.extra.CANCEL_UI_REQUEST";
 
     @NonNull
     private final IBinder mToken;
@@ -51,6 +54,10 @@
         return mToken;
     }
 
+    /**
+     * Returns the app package name invoking this request, that can be used to derive display
+     * metadata (e.g. "Cancelled by `App Name`").
+     */
     @NonNull
     public String getAppPackageName() {
         return mAppPackageName;
@@ -64,6 +71,7 @@
         return mShouldShowCancellationUi;
     }
 
+    /** Constructs a {@link CancelUiRequest}. */
     public CancelUiRequest(@NonNull IBinder token, boolean shouldShowCancellationUi,
             @NonNull String appPackageName) {
         mToken = token;
@@ -91,7 +99,8 @@
         return 0;
     }
 
-    @NonNull public static final Creator<CancelUiRequest> CREATOR = new Creator<>() {
+    @NonNull
+    public static final Creator<CancelUiRequest> CREATOR = new Creator<>() {
         @Override
         public CancelUiRequest createFromParcel(@NonNull Parcel in) {
             return new CancelUiRequest(in);
diff --git a/core/java/android/credentials/ui/Constants.java b/core/java/android/credentials/ui/Constants.java
index 37f850b..68f28e7 100644
--- a/core/java/android/credentials/ui/Constants.java
+++ b/core/java/android/credentials/ui/Constants.java
@@ -36,7 +36,5 @@
     public static final String EXTRA_REQ_FOR_ALL_OPTIONS =
             "android.credentials.ui.extra.REQ_FOR_ALL_OPTIONS";
 
-    /** The intent action for when the enabled Credential Manager providers has been updated. */
-    public static final String CREDMAN_ENABLED_PROVIDERS_UPDATED =
-            "android.credentials.ui.action.CREDMAN_ENABLED_PROVIDERS_UPDATED";
+    private Constants() {}
 }
diff --git a/core/java/android/credentials/ui/CreateCredentialProviderData.java b/core/java/android/credentials/ui/CreateCredentialProviderData.java
index 2508d8e..d7a4f5b 100644
--- a/core/java/android/credentials/ui/CreateCredentialProviderData.java
+++ b/core/java/android/credentials/ui/CreateCredentialProviderData.java
@@ -47,6 +47,17 @@
         mRemoteEntry = remoteEntry;
     }
 
+    /**
+     * Converts the instance to a {@link CreateCredentialProviderInfo}.
+     *
+     * @hide
+     */
+    @NonNull
+    public CreateCredentialProviderInfo toCreateCredentialProviderInfo() {
+        return new CreateCredentialProviderInfo(
+                getProviderFlattenedComponentName(), mSaveEntries, mRemoteEntry);
+    }
+
     @NonNull
     public List<Entry> getSaveEntries() {
         return mSaveEntries;
diff --git a/core/java/android/credentials/ui/CreateCredentialProviderInfo.java b/core/java/android/credentials/ui/CreateCredentialProviderInfo.java
new file mode 100644
index 0000000..41ca852
--- /dev/null
+++ b/core/java/android/credentials/ui/CreateCredentialProviderInfo.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.ui;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Information pertaining to a specific provider during the given create-credential flow.
+ *
+ * This includes provider metadata and its credential creation options for display purposes.
+ *
+ * @hide
+ */
+public final class CreateCredentialProviderInfo {
+
+    @NonNull
+    private final String mProviderName;
+
+    @NonNull
+    private final List<Entry> mSaveEntries;
+    @Nullable
+    private final Entry mRemoteEntry;
+
+    CreateCredentialProviderInfo(
+            @NonNull String providerName, @NonNull List<Entry> saveEntries,
+            @Nullable Entry remoteEntry) {
+        mProviderName = Preconditions.checkStringNotEmpty(providerName);
+        mSaveEntries = new ArrayList<>(saveEntries);
+        mRemoteEntry = remoteEntry;
+    }
+
+    /** Returns the fully-qualified provider (component or package) name. */
+    @NonNull
+    public String getProviderName() {
+        return mProviderName;
+    }
+
+    /** Returns all the options this provider has, to which the credential can be saved. */
+    @NonNull
+    public List<Entry> getSaveEntries() {
+        return mSaveEntries;
+    }
+
+    /**
+     * Returns the remote credential saving option, if any.
+     *
+     * Notice that only one system configured provider can set this option, and when set, it means
+     * that the system service has already validated the provider's eligibility.
+     */
+    @Nullable
+    public Entry getRemoteEntry() {
+        return mRemoteEntry;
+    }
+
+    /**
+     * Builder for {@link CreateCredentialProviderInfo}.
+     *
+     * @hide
+     */
+    public static final class Builder {
+        @NonNull
+        private String mProviderName;
+        @NonNull
+        private List<Entry> mSaveEntries = new ArrayList<>();
+        @Nullable
+        private Entry mRemoteEntry = null;
+
+        /** Constructor with required properties. */
+        public Builder(@NonNull String providerName) {
+            mProviderName = Preconditions.checkStringNotEmpty(providerName);
+        }
+
+        /** Sets the list of options for credential saving to be displayed to the user. */
+        @NonNull
+        public Builder setSaveEntries(@NonNull List<Entry> credentialEntries) {
+            mSaveEntries = credentialEntries;
+            return this;
+        }
+
+        /** Sets the remote entry of the provider. */
+        @NonNull
+        public Builder setRemoteEntry(@Nullable Entry remoteEntry) {
+            mRemoteEntry = remoteEntry;
+            return this;
+        }
+
+        /** Builds a {@link CreateCredentialProviderInfo}. */
+        @NonNull
+        public CreateCredentialProviderInfo build() {
+            return new CreateCredentialProviderInfo(mProviderName, mSaveEntries, mRemoteEntry);
+        }
+    }
+}
diff --git a/core/java/android/credentials/ui/DisabledProviderData.java b/core/java/android/credentials/ui/DisabledProviderData.java
index c266fd5..8bccdc9 100644
--- a/core/java/android/credentials/ui/DisabledProviderData.java
+++ b/core/java/android/credentials/ui/DisabledProviderData.java
@@ -34,6 +34,16 @@
         super(providerFlattenedComponentName);
     }
 
+    /**
+     * Converts the instance to a {@link DisabledProviderInfo}.
+     *
+     * @hide
+     */
+    @NonNull
+    public DisabledProviderInfo toDisabledProviderInfo() {
+        return new DisabledProviderInfo(getProviderFlattenedComponentName());
+    }
+
     private DisabledProviderData(@NonNull Parcel in) {
         super(in);
     }
diff --git a/core/java/android/credentials/ui/DisabledProviderInfo.java b/core/java/android/credentials/ui/DisabledProviderInfo.java
new file mode 100644
index 0000000..7ce6368
--- /dev/null
+++ b/core/java/android/credentials/ui/DisabledProviderInfo.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.ui;
+
+import android.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Information pertaining to a specific provider that is disabled from the user settings.
+ *
+ * Currently, disabled provider data is only propagated in the create-credential flow.
+ *
+ * @hide
+ */
+public final class DisabledProviderInfo {
+
+    @NonNull
+    private final String mProviderName;
+
+    /**
+     * Constructs a {@link DisabledProviderInfo}.
+     *
+     * @throws IllegalArgumentException if {@code providerName} is empty
+     */
+    public DisabledProviderInfo(
+            @NonNull String providerName) {
+        mProviderName = Preconditions.checkStringNotEmpty(providerName);
+    }
+
+    /** Returns the fully-qualified provider (component or package) name. */
+    @NonNull
+    public String getProviderName() {
+        return mProviderName;
+    }
+}
diff --git a/core/java/android/credentials/ui/Entry.java b/core/java/android/credentials/ui/Entry.java
index 55f2a3e..8469447 100644
--- a/core/java/android/credentials/ui/Entry.java
+++ b/core/java/android/credentials/ui/Entry.java
@@ -35,10 +35,14 @@
  */
 @TestApi
 public final class Entry implements Parcelable {
-    @NonNull private final String mKey;
-    @NonNull private final String mSubkey;
-    @Nullable private PendingIntent mPendingIntent;
-    @Nullable private Intent mFrameworkExtrasIntent;
+    @NonNull
+    private final String mKey;
+    @NonNull
+    private final String mSubkey;
+    @Nullable
+    private PendingIntent mPendingIntent;
+    @Nullable
+    private Intent mFrameworkExtrasIntent;
 
     @NonNull
     private final Slice mSlice;
@@ -58,16 +62,19 @@
         mFrameworkExtrasIntent = in.readTypedObject(Intent.CREATOR);
     }
 
-    /** Constructor to be used for an entry that does not require further activities
+    /**
+     * Constructor to be used for an entry that does not require further activities
      * to be invoked when selected.
      */
+    // TODO(b/322065508): deprecate this constructor.
     public Entry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice) {
         mKey = key;
         mSubkey = subkey;
         mSlice = slice;
     }
 
-    /** Constructor to be used for an entry that requires a pending intent to be invoked
+    /**
+     * Constructor to be used for an entry that requires a pending intent to be invoked
      * when clicked.
      */
     public Entry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice,
@@ -77,9 +84,12 @@
     }
 
     /**
-    * Returns the identifier of this entry that's unique within the context of the CredentialManager
-    * request.
-    */
+     * Returns the identifier of this entry that's unique within the context of the
+     * CredentialManager
+     * request.
+     *
+     * Generally used when sending the user selection result back to the system service.
+     */
     @NonNull
     public String getKey() {
         return mKey;
@@ -87,25 +97,33 @@
 
     /**
      * Returns the sub-identifier of this entry that's unique within the context of the {@code key}.
+     *
+     * Generally used when sending the user selection result back to the system service.
      */
     @NonNull
     public String getSubkey() {
         return mSubkey;
     }
 
-    /**
-    * Returns the Slice to be rendered.
-    */
+    /** Returns the Slice to be rendered. */
     @NonNull
     public Slice getSlice() {
         return mSlice;
     }
 
+    /**
+     * Returns the provider PendingIntent to launch once this entry is selected.
+     */
+    // TODO(b/322065508): deprecate this bit.
     @Nullable
     public PendingIntent getPendingIntent() {
         return mPendingIntent;
     }
 
+    /**
+     * Returns the framework fill in intent to add to the provider PendingIntent to launch, once
+     * this entry is selected.
+     */
     @Nullable
     @SuppressLint("IntentBuilderName") // Not building a new intent.
     public Intent getFrameworkExtrasIntent() {
@@ -126,7 +144,7 @@
         return 0;
     }
 
-    public static final @NonNull Creator<Entry> CREATOR = new Creator<Entry>() {
+    public static final @NonNull Creator<Entry> CREATOR = new Creator<>() {
         @Override
         public Entry createFromParcel(@NonNull Parcel in) {
             return new Entry(in);
diff --git a/core/java/android/credentials/ui/FailureDialogResult.java b/core/java/android/credentials/ui/FailureDialogResult.java
new file mode 100644
index 0000000..abd5a92
--- /dev/null
+++ b/core/java/android/credentials/ui/FailureDialogResult.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.ui;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Result data when the selector UI has encountered a failure.
+ *
+ * @hide
+ */
+public final class FailureDialogResult extends BaseDialogResult implements Parcelable {
+    /** Parses and returns a UserSelectionDialogResult from the given resultData. */
+    @Nullable
+    public static FailureDialogResult fromResultData(@NonNull Bundle resultData) {
+        return resultData.getParcelable(
+                EXTRA_FAILURE_RESULT, FailureDialogResult.class);
+    }
+
+    /**
+     * Used for the UX to construct the {@code resultData Bundle} to send via the {@code
+     * ResultReceiver}.
+     */
+    public static void addToBundle(
+            @NonNull FailureDialogResult result, @NonNull Bundle bundle) {
+        bundle.putParcelable(EXTRA_FAILURE_RESULT, result);
+    }
+
+    /**
+     * The intent extra key for the {@code UserSelectionDialogResult} object when the credential
+     * selector activity finishes.
+     */
+    private static final String EXTRA_FAILURE_RESULT =
+            "android.credentials.ui.extra.FAILURE_RESULT";
+
+    @Nullable
+    private final String mErrorMessage;
+
+    public FailureDialogResult(@Nullable IBinder requestToken, @Nullable String errorMessage) {
+        super(requestToken);
+        mErrorMessage = errorMessage;
+    }
+
+    /** Returns provider package name whose entry was selected by the user. */
+    @Nullable
+    public String getErrorMessage() {
+        return mErrorMessage;
+    }
+
+    protected FailureDialogResult(@NonNull Parcel in) {
+        super(in);
+        mErrorMessage = in.readString8();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeString8(mErrorMessage);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Creator<FailureDialogResult> CREATOR =
+            new Creator<>() {
+                @Override
+                public FailureDialogResult createFromParcel(@NonNull Parcel in) {
+                    return new FailureDialogResult(in);
+                }
+
+                @Override
+                public FailureDialogResult[] newArray(int size) {
+                    return new FailureDialogResult[size];
+                }
+            };
+}
diff --git a/core/java/android/credentials/ui/FailureResult.java b/core/java/android/credentials/ui/FailureResult.java
new file mode 100644
index 0000000..ec58417
--- /dev/null
+++ b/core/java/android/credentials/ui/FailureResult.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.ui;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Failure or cancellation result encountered during a UI flow.
+ *
+ * @hide
+ */
+public final class FailureResult implements UiResult {
+    @Nullable
+    private final String mErrorMessage;
+    @NonNull
+    private final int mErrorCode;
+
+    /** @hide **/
+    @IntDef(prefix = {"ERROR_CODE_"}, value = {
+            ERROR_CODE_DIALOG_CANCELED_BY_USER,
+            ERROR_CODE_CANCELED_AND_LAUNCHED_SETTINGS,
+            ERROR_CODE_UI_FAILURE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ErrorCode {
+    }
+
+    /**
+     * The UI was stopped due to a failure, e.g. because it failed to parse the incoming data,
+     * or it encountered an irrecoverable internal issue.
+     */
+    public static final int ERROR_CODE_UI_FAILURE = 0;
+    /** The user intentionally canceled the dialog. */
+    public static final int ERROR_CODE_DIALOG_CANCELED_BY_USER = 1;
+    /**
+     * The UI was stopped since the user has chosen to navigate to the Settings UI to reconfigure
+     * their providers.
+     */
+    public static final int ERROR_CODE_CANCELED_AND_LAUNCHED_SETTINGS = 2;
+
+    /**
+     * Constructs a {@link FailureResult}.
+     *
+     * @throws IllegalArgumentException if {@code providerId} is empty
+     */
+    public FailureResult(@ErrorCode int errorCode, @Nullable String errorMessage) {
+        mErrorCode = errorCode;
+        mErrorMessage = errorMessage;
+    }
+
+    /** Returns the error code. */
+    @ErrorCode
+    public int getErrorCode() {
+        return mErrorCode;
+    }
+
+    /** Returns the error message. */
+    @Nullable
+    public String getErrorMessage() {
+        return mErrorMessage;
+    }
+
+    FailureDialogResult toFailureDialogResult() {
+        return new FailureDialogResult(/*requestToken=*/null, mErrorMessage);
+    }
+
+    int errorCodeToResultCode() {
+        switch (mErrorCode) {
+            case ERROR_CODE_DIALOG_CANCELED_BY_USER:
+                return BaseDialogResult.RESULT_CODE_DIALOG_USER_CANCELED;
+            case ERROR_CODE_CANCELED_AND_LAUNCHED_SETTINGS:
+                return BaseDialogResult.RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS;
+            default:
+                return BaseDialogResult.RESULT_CODE_DATA_PARSING_FAILURE;
+        }
+    }
+}
diff --git a/core/java/android/credentials/ui/GetCredentialProviderData.java b/core/java/android/credentials/ui/GetCredentialProviderData.java
index 181475c..481419b 100644
--- a/core/java/android/credentials/ui/GetCredentialProviderData.java
+++ b/core/java/android/credentials/ui/GetCredentialProviderData.java
@@ -55,6 +55,17 @@
         mRemoteEntry = remoteEntry;
     }
 
+    /**
+     * Converts the instance to a {@link GetCredentialProviderInfo}.
+     *
+     * @hide
+     */
+    @NonNull
+    public GetCredentialProviderInfo toGetCredentialProviderInfo() {
+        return new GetCredentialProviderInfo(getProviderFlattenedComponentName(),
+                mCredentialEntries, mActionChips, mAuthenticationEntries, mRemoteEntry);
+    }
+
     @NonNull
     public List<Entry> getCredentialEntries() {
         return mCredentialEntries;
@@ -83,12 +94,12 @@
         mCredentialEntries = credentialEntries;
         AnnotationValidations.validate(NonNull.class, null, mCredentialEntries);
 
-        List<Entry> actionChips  = new ArrayList<>();
+        List<Entry> actionChips = new ArrayList<>();
         in.readTypedList(actionChips, Entry.CREATOR);
         mActionChips = actionChips;
         AnnotationValidations.validate(NonNull.class, null, mActionChips);
 
-        List<AuthenticationEntry> authenticationEntries  = new ArrayList<>();
+        List<AuthenticationEntry> authenticationEntries = new ArrayList<>();
         in.readTypedList(authenticationEntries, AuthenticationEntry.CREATOR);
         mAuthenticationEntries = authenticationEntries;
         AnnotationValidations.validate(NonNull.class, null, mAuthenticationEntries);
@@ -113,16 +124,16 @@
 
     public static final @NonNull Creator<GetCredentialProviderData> CREATOR =
             new Creator<GetCredentialProviderData>() {
-        @Override
-        public GetCredentialProviderData createFromParcel(@NonNull Parcel in) {
-            return new GetCredentialProviderData(in);
-        }
+                @Override
+                public GetCredentialProviderData createFromParcel(@NonNull Parcel in) {
+                    return new GetCredentialProviderData(in);
+                }
 
-        @Override
-        public GetCredentialProviderData[] newArray(int size) {
-            return new GetCredentialProviderData[size];
-        }
-    };
+                @Override
+                public GetCredentialProviderData[] newArray(int size) {
+                    return new GetCredentialProviderData[size];
+                }
+            };
 
     /**
      * Builder for {@link GetCredentialProviderData}.
@@ -131,11 +142,16 @@
      */
     @TestApi
     public static final class Builder {
-        @NonNull private String mProviderFlattenedComponentName;
-        @NonNull private List<Entry> mCredentialEntries = new ArrayList<>();
-        @NonNull private List<Entry> mActionChips = new ArrayList<>();
-        @NonNull private List<AuthenticationEntry> mAuthenticationEntries = new ArrayList<>();
-        @Nullable private Entry mRemoteEntry = null;
+        @NonNull
+        private String mProviderFlattenedComponentName;
+        @NonNull
+        private List<Entry> mCredentialEntries = new ArrayList<>();
+        @NonNull
+        private List<Entry> mActionChips = new ArrayList<>();
+        @NonNull
+        private List<AuthenticationEntry> mAuthenticationEntries = new ArrayList<>();
+        @Nullable
+        private Entry mRemoteEntry = null;
 
         /** Constructor with required properties. */
         public Builder(@NonNull String providerFlattenedComponentName) {
diff --git a/core/java/android/credentials/ui/GetCredentialProviderInfo.java b/core/java/android/credentials/ui/GetCredentialProviderInfo.java
new file mode 100644
index 0000000..bac7147
--- /dev/null
+++ b/core/java/android/credentials/ui/GetCredentialProviderInfo.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.ui;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Information pertaining to a specific provider during the given create-credential flow.
+ *
+ * This includes provider metadata and its credential creation options for display purposes.
+ *
+ * @hide
+ */
+public final class GetCredentialProviderInfo {
+
+    @NonNull
+    private final String mProviderName;
+
+    @NonNull
+    private final List<Entry> mCredentialEntries;
+    @NonNull
+    private final List<Entry> mActionChips;
+    @NonNull
+    private final List<AuthenticationEntry> mAuthenticationEntries;
+    @Nullable
+    private final Entry mRemoteEntry;
+
+    GetCredentialProviderInfo(
+            @NonNull String providerName, @NonNull List<Entry> credentialEntries,
+            @NonNull List<Entry> actionChips,
+            @NonNull List<AuthenticationEntry> authenticationEntries,
+            @Nullable Entry remoteEntry) {
+        mProviderName = Preconditions.checkStringNotEmpty(providerName);
+        mCredentialEntries = new ArrayList<>(credentialEntries);
+        mActionChips = new ArrayList<>(actionChips);
+        mAuthenticationEntries = new ArrayList<>(authenticationEntries);
+        mRemoteEntry = remoteEntry;
+    }
+
+    /** Returns the fully-qualified provider (component or package) name. */
+    @NonNull
+    public String getProviderName() {
+        return mProviderName;
+    }
+
+    /** Returns the display information for all the candidate credentials this provider has. */
+    @NonNull
+    public List<Entry> getCredentialEntries() {
+        return mCredentialEntries;
+    }
+
+    /**
+     * Returns a list of actions defined by the provider that intent into the provider's app for
+     * specific user actions, each of which should eventually lead to an actual credential.
+     */
+    @NonNull
+    public List<Entry> getActionChips() {
+        return mActionChips;
+    }
+
+    /**
+     * Returns a list of authentication actions that each intents into a provider authentication
+     * activity.
+     *
+     * When the authentication activity succeeds, the provider will return a list of actual
+     * credential candidates to render. However, the UI should not attempt to parse the result
+     * itself, but rather send the result back to the system service, which will then process the
+     * new candidates and relaunch the UI with updated display data.
+     */
+    @NonNull
+    public List<AuthenticationEntry> getAuthenticationEntries() {
+        return mAuthenticationEntries;
+    }
+
+    /**
+     * Returns the remote credential retrieval option, if any.
+     *
+     * Notice that only one system configured provider can set this option, and when set, it means
+     * that the system service has already validated the provider's eligibility.
+     */
+    @Nullable
+    public Entry getRemoteEntry() {
+        return mRemoteEntry;
+    }
+
+    /**
+     * Builder for {@link GetCredentialProviderInfo}.
+     *
+     * @hide
+     */
+    public static final class Builder {
+        @NonNull
+        private String mProviderName;
+        @NonNull
+        private List<Entry> mCredentialEntries = new ArrayList<>();
+        @NonNull
+        private List<Entry> mActionChips = new ArrayList<>();
+        @NonNull
+        private List<AuthenticationEntry> mAuthenticationEntries = new ArrayList<>();
+        @Nullable
+        private Entry mRemoteEntry = null;
+
+        /**
+         * Constructs a {@link GetCredentialProviderInfo.Builder}.
+         *
+         * @throws IllegalArgumentException if {@code providerName} is null or empty
+         */
+        public Builder(@NonNull String providerName) {
+            mProviderName = Preconditions.checkStringNotEmpty(providerName);
+        }
+
+        /** Sets the list of credential candidates to be displayed to the user. */
+        @NonNull
+        public Builder setCredentialEntries(@NonNull List<Entry> credentialEntries) {
+            mCredentialEntries = credentialEntries;
+            return this;
+        }
+
+        /** Sets the list of action chips to be displayed to the user. */
+        @NonNull
+        public Builder setActionChips(@NonNull List<Entry> actionChips) {
+            mActionChips = actionChips;
+            return this;
+        }
+
+        /** Sets the authentication entry to be displayed to the user. */
+        @NonNull
+        public Builder setAuthenticationEntries(
+                @NonNull List<AuthenticationEntry> authenticationEntry) {
+            mAuthenticationEntries = authenticationEntry;
+            return this;
+        }
+
+        /** Sets the remote entry to be displayed to the user. */
+        @NonNull
+        public Builder setRemoteEntry(@Nullable Entry remoteEntry) {
+            mRemoteEntry = remoteEntry;
+            return this;
+        }
+
+        /** Builds a {@link GetCredentialProviderInfo}. */
+        @NonNull
+        public GetCredentialProviderInfo build() {
+            return new GetCredentialProviderInfo(mProviderName,
+                    mCredentialEntries, mActionChips, mAuthenticationEntries, mRemoteEntry);
+        }
+    }
+}
diff --git a/core/java/android/credentials/ui/IntentFactory.java b/core/java/android/credentials/ui/IntentFactory.java
index 49321d5..5e1e0ef 100644
--- a/core/java/android/credentials/ui/IntentFactory.java
+++ b/core/java/android/credentials/ui/IntentFactory.java
@@ -113,25 +113,6 @@
     }
 
     /**
-     * Notify the UI that providers have been enabled/disabled.
-     *
-     * @hide
-     */
-    @NonNull
-    public static Intent createProviderUpdateIntent() {
-        Intent intent = new Intent();
-        ComponentName componentName =
-                ComponentName.unflattenFromString(
-                        Resources.getSystem()
-                                .getString(
-                                        com.android.internal.R.string
-                                                .config_credentialManagerReceiverComponent));
-        intent.setComponent(componentName);
-        intent.setAction(Constants.CREDMAN_ENABLED_PROVIDERS_UPDATED);
-        return intent;
-    }
-
-    /**
      * Convert an instance of a "locally-defined" ResultReceiver to an instance of {@link
      * android.os.ResultReceiver} itself, which the receiving process will be able to unmarshall.
      */
diff --git a/core/java/android/credentials/ui/IntentHelper.java b/core/java/android/credentials/ui/IntentHelper.java
new file mode 100644
index 0000000..c5f34c1
--- /dev/null
+++ b/core/java/android/credentials/ui/IntentHelper.java
@@ -0,0 +1,97 @@
+/*
+ * 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.credentials.ui;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.content.Intent;
+import android.os.ResultReceiver;
+
+import java.util.List;
+
+/**
+ * Utilities for parsing the intent data used to launch the UI activity.
+ *
+ * @hide
+ */
+public final class IntentHelper {
+    /**
+     * Attempts to extract a {@link CancelUiRequest} from the given intent; returns null
+     * if not found.
+     */
+    @Nullable
+    public static CancelUiRequest extractCancelUiRequest(@NonNull Intent intent) {
+        return intent.getParcelableExtra(CancelUiRequest.EXTRA_CANCEL_UI_REQUEST,
+                CancelUiRequest.class);
+    }
+
+    /**
+     * Attempts to extract a {@link RequestInfo} from the given intent; returns null
+     * if not found.
+     */
+    @Nullable
+    public static RequestInfo extractRequestInfo(@NonNull Intent intent) {
+        return intent.getParcelableExtra(RequestInfo.EXTRA_REQUEST_INFO,
+                RequestInfo.class);
+    }
+
+    /**
+     * Attempts to extract the list of {@link GetCredentialProviderInfo} from the given intent;
+     * returns null if not found.
+     */
+    @Nullable
+    @SuppressLint("NullableCollection") // To be consistent with the nullable Intent extra APIs
+    // and the other APIs in this class.
+    public static List<GetCredentialProviderInfo> extractGetCredentialProviderDataList(
+            @NonNull Intent intent) {
+        List<GetCredentialProviderData> providerList = intent.getParcelableArrayListExtra(
+                ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
+                GetCredentialProviderData.class);
+        return providerList == null ? null : providerList.stream().map(
+                GetCredentialProviderData::toGetCredentialProviderInfo).toList();
+    }
+
+    /**
+     * Attempts to extract the list of {@link CreateCredentialProviderInfo} from the given intent;
+     * returns null if not found.
+     */
+    @Nullable
+    @SuppressLint("NullableCollection") // To be consistent with the nullable Intent extra APIs
+    // and the other APIs in this class.
+    public static List<CreateCredentialProviderInfo> extractCreateCredentialProviderDataList(
+            @NonNull Intent intent) {
+        List<CreateCredentialProviderData> providerList = intent.getParcelableArrayListExtra(
+                ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
+                CreateCredentialProviderData.class);
+        return providerList == null ? null : providerList.stream().map(
+                CreateCredentialProviderData::toCreateCredentialProviderInfo).toList();
+    }
+
+    /**
+     * Attempts to extract a {@link android.os.ResultReceiver} from the given intent, which should
+     * be used to send back UI results; returns null if not found.
+     */
+    @Nullable
+    public static ResultReceiver extractResultReceiver(@NonNull Intent intent) {
+        return intent.getParcelableExtra(Constants.EXTRA_RESULT_RECEIVER,
+                ResultReceiver.class);
+    }
+
+    private IntentHelper() {
+    }
+}
diff --git a/core/java/android/credentials/ui/ProviderDialogResult.java b/core/java/android/credentials/ui/ProviderDialogResult.java
deleted file mode 100644
index 53f1864..0000000
--- a/core/java/android/credentials/ui/ProviderDialogResult.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.credentials.ui;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.AnnotationValidations;
-
-/**
- * Result data matching {@link BaseDialogResult#RESULT_CODE_PROVIDER_ENABLED}, or {@link
- * BaseDialogResult#RESULT_CODE_DEFAULT_PROVIDER_CHANGED}.
- *
- * @hide
- */
-public final class ProviderDialogResult extends BaseDialogResult implements Parcelable {
-    /** Parses and returns a ProviderDialogResult from the given resultData. */
-    @Nullable
-    public static ProviderDialogResult fromResultData(@NonNull Bundle resultData) {
-        return resultData.getParcelable(EXTRA_PROVIDER_RESULT, ProviderDialogResult.class);
-    }
-
-    /**
-     * Used for the UX to construct the {@code resultData Bundle} to send via the {@code
-     *  ResultReceiver}.
-     */
-    public static void addToBundle(
-            @NonNull ProviderDialogResult result, @NonNull Bundle bundle) {
-        bundle.putParcelable(EXTRA_PROVIDER_RESULT, result);
-    }
-
-    /**
-     * The intent extra key for the {@code ProviderDialogResult} object when the credential
-     * selector activity finishes.
-     */
-    private static final String EXTRA_PROVIDER_RESULT =
-            "android.credentials.ui.extra.PROVIDER_RESULT";
-
-    @NonNull
-    private final String mProviderId;
-
-    public ProviderDialogResult(@NonNull IBinder requestToken, @NonNull String providerId) {
-        super(requestToken);
-        mProviderId = providerId;
-    }
-
-    @NonNull
-    public String getProviderId() {
-        return mProviderId;
-    }
-
-    protected ProviderDialogResult(@NonNull Parcel in) {
-        super(in);
-        String providerId = in.readString8();
-        mProviderId = providerId;
-        AnnotationValidations.validate(NonNull.class, null, mProviderId);
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        super.writeToParcel(dest, flags);
-        dest.writeString8(mProviderId);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    public static final @NonNull Creator<ProviderDialogResult> CREATOR =
-            new Creator<ProviderDialogResult>() {
-        @Override
-        public ProviderDialogResult createFromParcel(@NonNull Parcel in) {
-            return new ProviderDialogResult(in);
-        }
-
-        @Override
-        public ProviderDialogResult[] newArray(int size) {
-            return new ProviderDialogResult[size];
-        }
-    };
-}
diff --git a/core/java/android/credentials/ui/ProviderPendingIntentResponse.java b/core/java/android/credentials/ui/ProviderPendingIntentResponse.java
index 47936c4..11cc21f9 100644
--- a/core/java/android/credentials/ui/ProviderPendingIntentResponse.java
+++ b/core/java/android/credentials/ui/ProviderPendingIntentResponse.java
@@ -16,15 +16,20 @@
 
 package android.credentials.ui;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.content.Intent;
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import androidx.annotation.NonNull;
-
 /**
- * Response from a provider's pending intent
+ * Result of launching a provider's PendingIntent associated with an {@link Entry} after it is
+ * selected by the user.
+ *
+ * The provider sets the credential creation / retrieval result through
+ * {@link android.app.Activity#setResult(int, Intent)}, which is then directly propagated back
+ * through this data structure.
  *
  * @hide
  */
@@ -33,20 +38,21 @@
     @Nullable
     private final Intent mResultData;
 
+    /** Constructs a {@link ProviderPendingIntentResponse}. */
     public ProviderPendingIntentResponse(int resultCode, @Nullable Intent resultData) {
         mResultCode = resultCode;
         mResultData = resultData;
     }
 
-    protected ProviderPendingIntentResponse(Parcel in) {
+    private ProviderPendingIntentResponse(@NonNull Parcel in) {
         mResultCode = in.readInt();
         mResultData = in.readTypedObject(Intent.CREATOR);
     }
 
-    public static final Creator<ProviderPendingIntentResponse> CREATOR =
-            new Creator<ProviderPendingIntentResponse>() {
+    public static final @NonNull Creator<ProviderPendingIntentResponse> CREATOR =
+            new Creator<>() {
                 @Override
-                public ProviderPendingIntentResponse createFromParcel(Parcel in) {
+                public ProviderPendingIntentResponse createFromParcel(@NonNull Parcel in) {
                     return new ProviderPendingIntentResponse(in);
                 }
 
@@ -67,13 +73,15 @@
         dest.writeTypedObject(mResultData, flags);
     }
 
-    /** Returns the result code associated with this pending intent activity result. */
+    /** Returns the result code associated with this provider PendingIntent activity result. */
     public int getResultCode() {
         return mResultCode;
     }
 
-    /** Returns the result data associated with this pending intent activity result. */
-    @NonNull public Intent getResultData() {
+    /** Returns the result data associated with this provider PendingIntent activity result. */
+    @SuppressLint("IntentBuilderName") // Not building a new intent.
+    @NonNull
+    public Intent getResultData() {
         return mResultData;
     }
 }
diff --git a/core/java/android/credentials/ui/RequestInfo.java b/core/java/android/credentials/ui/RequestInfo.java
index 4fedc83..f651584 100644
--- a/core/java/android/credentials/ui/RequestInfo.java
+++ b/core/java/android/credentials/ui/RequestInfo.java
@@ -48,17 +48,24 @@
     @NonNull public static final String EXTRA_REQUEST_INFO =
             "android.credentials.ui.extra.REQUEST_INFO";
 
-    /** Type value for any request that does not require UI. */
+    /**
+     * Type value for any request that does not require UI.
+     */
     @NonNull public static final String TYPE_UNDEFINED = "android.credentials.ui.TYPE_UNDEFINED";
-    /** Type value for a getCredential request. */
+    /**
+     * Type value for a getCredential request.
+     */
     @NonNull public static final String TYPE_GET = "android.credentials.ui.TYPE_GET";
-    /** Type value for a getCredential request that utilizes the credential registry.
+    /**
+     * Type value for a getCredential request that utilizes the credential registry.
      *
      * @hide
-     **/
+     */
     @NonNull public static final String TYPE_GET_VIA_REGISTRY =
             "android.credentials.ui.TYPE_GET_VIA_REGISTRY";
-    /** Type value for a createCredential request. */
+    /**
+     * Type value for a createCredential request.
+     */
     @NonNull public static final String TYPE_CREATE = "android.credentials.ui.TYPE_CREATE";
 
     /** @hide */
diff --git a/core/java/android/credentials/ui/ResultHelper.java b/core/java/android/credentials/ui/ResultHelper.java
new file mode 100644
index 0000000..7b9d5e8
--- /dev/null
+++ b/core/java/android/credentials/ui/ResultHelper.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.ui;
+
+import android.annotation.NonNull;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+
+/**
+ * Utilities for sending the UI results back to the system service.
+ *
+ * @hide
+ */
+public final class ResultHelper {
+    /**
+     * Sends the {@code failureResult} that caused the UI to stop back to the CredentialManager
+     * service.
+     *
+     * The {code resultReceiver} for a UI flow can be extracted from the UI launch intent via
+     * {@link IntentHelper#extractResultReceiver(Intent)}.
+     */
+    public static void sendFailureResult(@NonNull ResultReceiver resultReceiver,
+            @NonNull FailureResult failureResult) {
+        FailureDialogResult result = failureResult.toFailureDialogResult();
+        Bundle resultData = new Bundle();
+        FailureDialogResult.addToBundle(result, resultData);
+        resultReceiver.send(failureResult.errorCodeToResultCode(),
+                resultData);
+    }
+
+    /**
+     * Sends the completed {@code userSelectionResult} back to the CredentialManager service.
+     *
+     * The {code resultReceiver} for a UI flow can be extracted from the UI launch intent via
+     * {@link IntentHelper#extractResultReceiver(Intent)}.
+     */
+    public static void sendUserSelectionResult(@NonNull ResultReceiver resultReceiver,
+            @NonNull UserSelectionResult userSelectionResult) {
+        UserSelectionDialogResult result = userSelectionResult.toUserSelectionDialogResult();
+        Bundle resultData = new Bundle();
+        UserSelectionDialogResult.addToBundle(result, resultData);
+        resultReceiver.send(BaseDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION,
+                resultData);
+    }
+
+    private ResultHelper() {}
+}
diff --git a/core/java/android/credentials/ui/UiResult.java b/core/java/android/credentials/ui/UiResult.java
new file mode 100644
index 0000000..692584d
--- /dev/null
+++ b/core/java/android/credentials/ui/UiResult.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.ui;
+
+/**
+ * Base class for different types of ui results.
+ *
+ * @hide
+ */
+public interface UiResult {}
diff --git a/core/java/android/credentials/ui/UserSelectionResult.java b/core/java/android/credentials/ui/UserSelectionResult.java
new file mode 100644
index 0000000..431dc63
--- /dev/null
+++ b/core/java/android/credentials/ui/UserSelectionResult.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.ui;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Result sent back from the UI after the user chose an option and completed the following
+ * transaction launched through the provider PendingIntent associated with that option.
+ *
+ * @hide
+ */
+public final class UserSelectionResult implements UiResult {
+    @NonNull
+    private final String mProviderId;
+    @NonNull
+    private final String mEntryKey;
+    @NonNull
+    private final String mEntrySubkey;
+    @Nullable
+    private ProviderPendingIntentResponse mProviderPendingIntentResponse;
+
+    /**
+     * Constructs a {@link UserSelectionResult}.
+     *
+     * @throws IllegalArgumentException if {@code providerId} is empty
+     */
+
+    public UserSelectionResult(@NonNull String providerId,
+            @NonNull String entryKey, @NonNull String entrySubkey,
+            @Nullable ProviderPendingIntentResponse providerPendingIntentResponse) {
+        mProviderId = Preconditions.checkStringNotEmpty(providerId);
+        mEntryKey = Preconditions.checkNotNull(entryKey);
+        mEntrySubkey = Preconditions.checkNotNull(entrySubkey);
+        mProviderPendingIntentResponse = providerPendingIntentResponse;
+    }
+
+    /** Returns provider package name whose entry was selected by the user. */
+    @NonNull
+    public String getProviderId() {
+        return mProviderId;
+    }
+
+    /** Returns the key of the visual entry that the user selected. */
+    @NonNull
+    public String getEntryKey() {
+        return mEntryKey;
+    }
+
+    /** Returns the subkey of the visual entry that the user selected. */
+    @NonNull
+    public String getEntrySubkey() {
+        return mEntrySubkey;
+    }
+
+    /** Returns the pending intent response from the provider. */
+    @Nullable
+    public ProviderPendingIntentResponse getPendingIntentProviderResponse() {
+        return mProviderPendingIntentResponse;
+    }
+
+    @NonNull
+    UserSelectionDialogResult toUserSelectionDialogResult() {
+        return new UserSelectionDialogResult(/*requestToken=*/null, mProviderId, mEntryKey,
+                mEntrySubkey, mProviderPendingIntentResponse);
+    }
+}
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index 0047b7d..ce0f9f59 100644
--- a/core/java/android/hardware/HardwareBuffer.java
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -115,14 +115,16 @@
     @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_REQUESTED_FORMATS_V)
     public static final int R_8           = 0x38;
     /**
-     * Format: 16 bits red. Bits should be represented in unsigned integer, instead of the
-     * implicit unsigned normalized.
+     * Format: 16 bits red. When sampled on the GPU this is represented as an
+     * unsigned integer instead of implicit unsigned normalize.
+     * For more information see https://www.khronos.org/opengl/wiki/Normalized_Integer
      */
     @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_REQUESTED_FORMATS_V)
     public static final int R_16          = 0x39;
     /**
-     * Format: 16 bits each red, green. Bits should be represented in unsigned integer,
-     * instead of the implicit unsigned normalized.
+     * Format: 16 bits each red, green. When sampled on the GPU this is represented
+     * as an unsigned integer instead of implicit unsigned normalize.
+     * For more information see https://www.khronos.org/opengl/wiki/Normalized_Integer
      */
     @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_REQUESTED_FORMATS_V)
     public static final int RG_1616       = 0x3a;
diff --git a/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl b/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl
index 73ac333..d51e62e 100644
--- a/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl
+++ b/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl
@@ -33,4 +33,20 @@
      * Defines behavior in response to authentication stopping
      */
     void onAuthenticationStopped();
+
+    /**
+     * Defines behavior in response to a successful authentication
+     * @param requestReason Reason from [BiometricRequestConstants.RequestReason] for the requested
+     *                      authentication
+     * @param userId The user Id for the requested authentication
+     */
+    void onAuthenticationSucceeded(int requestReason, int userId);
+
+    /**
+     * Defines behavior in response to a failed authentication
+     * @param requestReason Reason from [BiometricRequestConstants.RequestReason] for the requested
+     *                      authentication
+     * @param userId The user Id for the requested authentication
+     */
+    void onAuthenticationFailed(int requestReason, int userId);
 }
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index c0424db..bdaf9d7 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.MANAGE_BIOMETRIC_DIALOG;
+import static android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO;
 import static android.Manifest.permission.TEST_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
@@ -174,9 +174,9 @@
          * @return This builder.
          */
         @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-        @RequiresPermission(MANAGE_BIOMETRIC_DIALOG)
+        @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO)
         @NonNull
-        public BiometricPrompt.Builder setLogo(@DrawableRes int logoRes) {
+        public BiometricPrompt.Builder setLogoRes(@DrawableRes int logoRes) {
             mPromptInfo.setLogoRes(logoRes);
             return this;
         }
@@ -193,9 +193,9 @@
          * @return This builder.
          */
         @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-        @RequiresPermission(MANAGE_BIOMETRIC_DIALOG)
+        @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO)
         @NonNull
-        public BiometricPrompt.Builder setLogo(@NonNull Bitmap logoBitmap) {
+        public BiometricPrompt.Builder setLogoBitmap(@NonNull Bitmap logoBitmap) {
             mPromptInfo.setLogoBitmap(logoBitmap);
             return this;
         }
@@ -719,25 +719,25 @@
 
     /**
      * Gets the drawable resource of the logo for the prompt, as set by
-     * {@link Builder#setLogo(int)}. Currently for system applications use only.
+     * {@link Builder#setLogoRes(int)}. Currently for system applications use only.
      *
      * @return The drawable resource of the logo, or -1 if the prompt has no logo resource set.
      */
     @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-    @RequiresPermission(MANAGE_BIOMETRIC_DIALOG)
+    @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO)
     @DrawableRes
     public int getLogoRes() {
         return mPromptInfo.getLogoRes();
     }
 
     /**
-     * Gets the logo bitmap for the prompt, as set by {@link Builder#setLogo(Bitmap)}. Currently for
-     * system applications use only.
+     * Gets the logo bitmap for the prompt, as set by {@link Builder#setLogoBitmap(Bitmap)}.
+     * Currently for system applications use only.
      *
      * @return The logo bitmap of the prompt, or null if the prompt has no logo bitmap set.
      */
     @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-    @RequiresPermission(MANAGE_BIOMETRIC_DIALOG)
+    @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO)
     @Nullable
     public Bitmap getLogoBitmap() {
         return mPromptInfo.getLogoBitmap();
diff --git a/core/java/android/hardware/biometrics/PromptContentItemBulletedText.java b/core/java/android/hardware/biometrics/PromptContentItemBulletedText.java
index c5e5a80..25e5cca 100644
--- a/core/java/android/hardware/biometrics/PromptContentItemBulletedText.java
+++ b/core/java/android/hardware/biometrics/PromptContentItemBulletedText.java
@@ -28,14 +28,14 @@
  */
 @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
 public final class PromptContentItemBulletedText implements PromptContentItemParcelable {
-    private final CharSequence mText;
+    private final String mText;
 
     /**
      * A list item with bulleted text shown on {@link PromptVerticalListContentView}.
      *
      * @param text The text of this list item.
      */
-    public PromptContentItemBulletedText(@NonNull CharSequence text) {
+    public PromptContentItemBulletedText(@NonNull String text) {
         mText = text;
     }
 
@@ -43,7 +43,7 @@
      * @hide
      */
     @NonNull
-    public CharSequence getText() {
+    public String getText() {
         return mText;
     }
 
@@ -60,7 +60,7 @@
      */
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeCharSequence(mText);
+        dest.writeString(mText);
     }
 
     /**
@@ -70,7 +70,7 @@
     public static final Creator<PromptContentItemBulletedText> CREATOR = new Creator<>() {
         @Override
         public PromptContentItemBulletedText createFromParcel(Parcel in) {
-            return new PromptContentItemBulletedText(in.readCharSequence());
+            return new PromptContentItemBulletedText(in.readString());
         }
 
         @Override
diff --git a/core/java/android/hardware/biometrics/PromptContentItemPlainText.java b/core/java/android/hardware/biometrics/PromptContentItemPlainText.java
index 6434c59..7919256 100644
--- a/core/java/android/hardware/biometrics/PromptContentItemPlainText.java
+++ b/core/java/android/hardware/biometrics/PromptContentItemPlainText.java
@@ -28,14 +28,14 @@
  */
 @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
 public final class PromptContentItemPlainText implements PromptContentItemParcelable {
-    private final CharSequence mText;
+    private final String mText;
 
     /**
      * A list item with plain text shown on {@link PromptVerticalListContentView}.
      *
      * @param text The text of this list item.
      */
-    public PromptContentItemPlainText(@NonNull CharSequence text) {
+    public PromptContentItemPlainText(@NonNull String text) {
         mText = text;
     }
 
@@ -43,7 +43,7 @@
      * @hide
      */
     @NonNull
-    public CharSequence getText() {
+    public String getText() {
         return mText;
     }
 
@@ -60,7 +60,7 @@
      */
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeCharSequence(mText);
+        dest.writeString(mText);
     }
 
     /**
@@ -70,7 +70,7 @@
     public static final Creator<PromptContentItemPlainText> CREATOR = new Creator<>() {
         @Override
         public PromptContentItemPlainText createFromParcel(Parcel in) {
-            return new PromptContentItemPlainText(in.readCharSequence());
+            return new PromptContentItemPlainText(in.readString());
         }
 
         @Override
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index d788b37..0f9cadc 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -166,9 +166,9 @@
     }
 
     /**
-     * Returns whether MANAGE_BIOMETRIC_DIALOG is contained.
+     * Returns whether SET_BIOMETRIC_DIALOG_LOGO is contained.
      */
-    public boolean containsManageBioApiConfigurations() {
+    public boolean containsSetLogoApiConfigurations() {
         if (mLogoRes != -1) {
             return true;
         } else if (mLogoBitmap != null) {
diff --git a/core/java/android/hardware/biometrics/PromptVerticalListContentView.java b/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
index f3e6290..38d32dc 100644
--- a/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
+++ b/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
@@ -52,11 +52,11 @@
     private static final int MAX_ITEM_NUMBER = 20;
     private static final int MAX_EACH_ITEM_CHARACTER_NUMBER = 640;
     private final List<PromptContentItemParcelable> mContentList;
-    private final CharSequence mDescription;
+    private final String mDescription;
 
     private PromptVerticalListContentView(
             @NonNull List<PromptContentItemParcelable> contentList,
-            @NonNull CharSequence description) {
+            @NonNull String description) {
         mContentList = contentList;
         mDescription = description;
     }
@@ -65,7 +65,7 @@
         mContentList = in.readArrayList(
                 PromptContentItemParcelable.class.getClassLoader(),
                 PromptContentItemParcelable.class);
-        mDescription = in.readCharSequence();
+        mDescription = in.readString();
     }
 
     /**
@@ -84,12 +84,12 @@
 
     /**
      * Gets the description for the content view, as set by
-     * {@link PromptVerticalListContentView.Builder#setDescription(CharSequence)}.
+     * {@link PromptVerticalListContentView.Builder#setDescription(String)}.
      *
      * @return The description for the content view, or null if the content view has no description.
      */
     @Nullable
-    public CharSequence getDescription() {
+    public String getDescription() {
         return mDescription;
     }
 
@@ -118,7 +118,7 @@
     @Override
     public void writeToParcel(@androidx.annotation.NonNull Parcel dest, int flags) {
         dest.writeList(mContentList);
-        dest.writeCharSequence(mDescription);
+        dest.writeString(mDescription);
     }
 
     /**
@@ -143,7 +143,7 @@
      */
     public static final class Builder {
         private final List<PromptContentItemParcelable> mContentList = new ArrayList<>();
-        private CharSequence mDescription;
+        private String mDescription;
 
         /**
          * Optional: Sets a description that will be shown on the content view.
@@ -152,7 +152,7 @@
          * @return This builder.
          */
         @NonNull
-        public Builder setDescription(@NonNull CharSequence description) {
+        public Builder setDescription(@NonNull String description) {
             mDescription = description;
             return this;
         }
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index e267e6b..8e234fa 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -15,6 +15,7 @@
  */
 package android.hardware.face;
 
+import android.hardware.biometrics.AuthenticationStateListener;
 import android.hardware.biometrics.IBiometricSensorReceiver;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.hardware.biometrics.IBiometricStateListener;
@@ -181,6 +182,14 @@
     // authenticators. The callback is automatically removed after it's invoked.
     void addAuthenticatorsRegisteredCallback(IFaceAuthenticatorsRegisteredCallback callback);
 
+    // Registers AuthenticationStateListener.
+    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+    void registerAuthenticationStateListener(AuthenticationStateListener listener);
+
+    // Unregisters AuthenticationStateListener.
+    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+    void unregisterAuthenticationStateListener(AuthenticationStateListener listener);
+
     // Registers BiometricStateListener.
     void registerBiometricStateListener(IBiometricStateListener listener);
 
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index d939532..fdbd319 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -17,6 +17,7 @@
 package android.hardware.input;
 
 import static com.android.hardware.input.Flags.keyboardA11yBounceKeysFlag;
+import static com.android.hardware.input.Flags.keyboardA11ySlowKeysFlag;
 import static com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag;
 import static com.android.input.flags.Flags.enableInputFilterRustImpl;
 
@@ -68,6 +69,12 @@
      */
     public static final int MAX_ACCESSIBILITY_BOUNCE_KEYS_THRESHOLD_MILLIS = 5000;
 
+    /**
+     * The maximum allowed Accessibility slow keys threshold.
+     * @hide
+     */
+    public static final int MAX_ACCESSIBILITY_SLOW_KEYS_THRESHOLD_MILLIS = 5000;
+
     private InputSettings() {
     }
 
@@ -419,6 +426,86 @@
     }
 
     /**
+     * Whether Accessibility slow keys feature flags is enabled.
+     *
+     * <p>
+     * 'Slow keys' is an accessibility feature to aid users who have physical disabilities, that
+     * allows the user to specify the duration for which one must press-and-hold a key before the
+     * system accepts the keypress.
+     * </p>
+     *
+     * @hide
+     */
+    public static boolean isAccessibilitySlowKeysFeatureFlagEnabled() {
+        return keyboardA11ySlowKeysFlag() && enableInputFilterRustImpl();
+    }
+
+    /**
+     * Whether Accessibility slow keys is enabled.
+     *
+     * <p>
+     * 'Slow keys' is an accessibility feature to aid users who have physical disabilities, that
+     * allows the user to specify the duration for which one must press-and-hold a key before the
+     * system accepts the keypress.
+     * </p>
+     *
+     * @hide
+     */
+    public static boolean isAccessibilitySlowKeysEnabled(@NonNull Context context) {
+        return getAccessibilitySlowKeysThreshold(context) != 0;
+    }
+
+    /**
+     * Get Accessibility slow keys threshold duration in milliseconds.
+     *
+     * <p>
+     * 'Slow keys' is an accessibility feature to aid users who have physical disabilities, that
+     * allows the user to specify the duration for which one must press-and-hold a key before the
+     * system accepts the keypress.
+     * </p>
+     *
+     * @hide
+     */
+    public static int getAccessibilitySlowKeysThreshold(@NonNull Context context) {
+        if (!isAccessibilitySlowKeysFeatureFlagEnabled()) {
+            return 0;
+        }
+        return Settings.Secure.getIntForUser(context.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_SLOW_KEYS, 0, UserHandle.USER_CURRENT);
+    }
+
+    /**
+     * Set Accessibility slow keys threshold duration in milliseconds.
+     * @param thresholdTimeMillis time duration for which a key should be pressed to be registered
+     *                            in the system. The threshold must be between 0 and
+     *                            {@link MAX_ACCESSIBILITY_SLOW_KEYS_THRESHOLD_MILLIS}
+     *
+     * <p>
+     * 'Slow keys' is an accessibility feature to aid users who have physical disabilities, that
+     * allows the user to specify the duration for which one must press-and-hold a key before the
+     * system accepts the keypress.
+     * </p>
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
+    public static void setAccessibilitySlowKeysThreshold(@NonNull Context context,
+            int thresholdTimeMillis) {
+        if (!isAccessibilitySlowKeysFeatureFlagEnabled()) {
+            return;
+        }
+        if (thresholdTimeMillis < 0
+                || thresholdTimeMillis > MAX_ACCESSIBILITY_SLOW_KEYS_THRESHOLD_MILLIS) {
+            throw new IllegalArgumentException(
+                    "Provided Slow keys threshold should be in range [0, "
+                            + MAX_ACCESSIBILITY_SLOW_KEYS_THRESHOLD_MILLIS + "]");
+        }
+        Settings.Secure.putIntForUser(context.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_SLOW_KEYS, thresholdTimeMillis,
+                UserHandle.USER_CURRENT);
+    }
+
+    /**
      * Whether Accessibility sticky keys feature is enabled.
      *
      * <p>
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 362fe78..0ed6569 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -29,4 +29,11 @@
     name: "pointer_coords_is_resampled_api"
     description: "Makes MotionEvent.PointerCoords#isResampled() a public API"
     bug: "298197511"
+}
+
+flag {
+    namespace: "input_native"
+    name: "keyboard_a11y_slow_keys_flag"
+    description: "Controls if the slow keys accessibility feature for physical keyboard is available to the user"
+    bug: "294546335"
 }
\ No newline at end of file
diff --git a/core/java/android/metrics/LogMaker.java b/core/java/android/metrics/LogMaker.java
index 8644d91..f65b713 100644
--- a/core/java/android/metrics/LogMaker.java
+++ b/core/java/android/metrics/LogMaker.java
@@ -32,6 +32,7 @@
  * @hide
  */
 @SystemApi
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class LogMaker {
     private static final String TAG = "LogBuilder";
 
diff --git a/core/java/android/net/thread/OWNERS b/core/java/android/net/thread/OWNERS
new file mode 100644
index 0000000..55c307b
--- /dev/null
+++ b/core/java/android/net/thread/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 1203089
+
+include platform/packages/modules/ThreadNetwork:/OWNERS
diff --git a/core/java/android/net/thread/flags.aconfig b/core/java/android/net/thread/flags.aconfig
new file mode 100644
index 0000000..6e72f8e
--- /dev/null
+++ b/core/java/android/net/thread/flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.net.thread.flags"
+
+flag {
+    name: "thread_user_restriction_enabled"
+    namespace: "thread_network"
+    description: "Controls whether user restriction on thread networks is enabled"
+    bug: "307679182"
+}
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 5871717..3977bdf 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -28,6 +28,7 @@
 import android.app.Application;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 import android.sysprop.DeviceProperties;
 import android.sysprop.SocProperties;
 import android.sysprop.TelephonyProperties;
@@ -47,6 +48,7 @@
 /**
  * Information about the current build, extracted from system properties.
  */
+@RavenwoodKeepWholeClass
 public class Build {
     private static final String TAG = "Build";
 
@@ -307,7 +309,7 @@
          * compatibility.
          */
         final String[] abiList;
-        if (VMRuntime.getRuntime().is64Bit()) {
+        if (android.os.Process.is64Bit()) {
             abiList = SUPPORTED_64_BIT_ABIS;
         } else {
             abiList = SUPPORTED_32_BIT_ABIS;
diff --git a/core/java/android/os/PersistableBundle.java b/core/java/android/os/PersistableBundle.java
index 02704f5..236194d 100644
--- a/core/java/android/os/PersistableBundle.java
+++ b/core/java/android/os/PersistableBundle.java
@@ -294,6 +294,43 @@
         XmlUtils.writeMapXml(mMap, out, this);
     }
 
+    /**
+     * Checks whether all keys and values are within the given character limit.
+     * Note: Maximum character limit of String that can be saved to XML as part of bundle is 65535.
+     * Otherwise IOException is thrown.
+     * @param limit length of String keys and values in the PersistableBundle, including nested
+     *                    PersistableBundles to check against.
+     *
+     * @hide
+     */
+    public boolean isBundleContentsWithinLengthLimit(int limit) {
+        unparcel();
+        if (mMap == null) {
+            return true;
+        }
+        for (int i = 0; i < mMap.size(); i++) {
+            if (mMap.keyAt(i) != null && mMap.keyAt(i).length() > limit) {
+                return false;
+            }
+            final Object value = mMap.valueAt(i);
+            if (value instanceof String && ((String) value).length() > limit) {
+                return false;
+            } else if (value instanceof String[]) {
+                String[] stringArray =  (String[]) value;
+                for (int j = 0; j < stringArray.length; j++) {
+                    if (stringArray[j] != null
+                            && stringArray[j].length() > limit) {
+                        return false;
+                    }
+                }
+            } else if (value instanceof PersistableBundle
+                    && !((PersistableBundle) value).isBundleContentsWithinLengthLimit(limit)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     /** @hide */
     static class MyReadMapCallback implements  XmlUtils.ReadMapCallback {
         @Override
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index dd0436c..1f3a162 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -1589,7 +1589,15 @@
     @UnsupportedAppUsage
     public static final native long getPss(int pid);
 
-    /** @hide */
+    /**
+     * Gets the total Rss value for a given process, in bytes.
+     *
+     * @param pid the process to the Rss for
+     * @return an ordered array containing multiple values, they are:
+     *  [total_rss, file, anon, swap, shmem].
+     *  or NULL if the value cannot be determined
+     * @hide
+     */
     public static final native long[] getRss(int pid);
 
     /**
diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java
index aa283a2..a818919 100644
--- a/core/java/android/os/SystemProperties.java
+++ b/core/java/android/os/SystemProperties.java
@@ -20,6 +20,8 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
+import android.ravenwood.annotation.RavenwoodNativeSubstitutionClass;
 import android.util.Log;
 import android.util.MutableInt;
 
@@ -36,6 +38,8 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Predicate;
 
 /**
  * Gives access to the system properties store.  The system properties
@@ -51,6 +55,8 @@
  * {@hide}
  */
 @SystemApi
+@RavenwoodKeepWholeClass
+@RavenwoodNativeSubstitutionClass("com.android.hoststubgen.nativesubstitution.SystemProperties_host")
 public class SystemProperties {
     private static final String TAG = "SystemProperties";
     private static final boolean TRACK_KEY_ACCESS = false;
@@ -94,6 +100,31 @@
         }
     }
 
+    /** @hide */
+    public static void init$ravenwood(Map<String, String> values,
+            Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate) {
+        native_init$ravenwood(values, keyReadablePredicate, keyWritablePredicate,
+                SystemProperties::callChangeCallbacks);
+        synchronized (sChangeCallbacks) {
+            sChangeCallbacks.clear();
+        }
+    }
+
+    /** @hide */
+    public static void reset$ravenwood() {
+        native_reset$ravenwood();
+        synchronized (sChangeCallbacks) {
+            sChangeCallbacks.clear();
+        }
+    }
+
+    // These native methods are currently only implemented by Ravenwood, as it's the only
+    // mechanism we have to jump to our RavenwoodNativeSubstitutionClass
+    private static native void native_init$ravenwood(Map<String, String> values,
+            Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate,
+            Runnable changeCallback);
+    private static native void native_reset$ravenwood();
+
     // The one-argument version of native_get used to be a regular native function. Nowadays,
     // we use the two-argument form of native_get all the time, but we can't just delete the
     // one-argument overload: apps use it via reflection, as the UnsupportedAppUsage annotation
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 5d7e04d..c0b4909 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -36,6 +36,7 @@
  * href="{@docRoot}tools/debugging/systrace.html">Analyzing Display and Performance
  * with Systrace</a>.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class Trace {
     /*
      * Writes trace events to the kernel trace buffer.  These trace events can be
@@ -123,10 +124,26 @@
 
     @UnsupportedAppUsage
     @CriticalNative
+    @android.ravenwood.annotation.RavenwoodReplace
     private static native long nativeGetEnabledTags();
+    @android.ravenwood.annotation.RavenwoodReplace
     private static native void nativeSetAppTracingAllowed(boolean allowed);
+    @android.ravenwood.annotation.RavenwoodReplace
     private static native void nativeSetTracingEnabled(boolean allowed);
 
+    private static long nativeGetEnabledTags$ravenwood() {
+        // Tracing currently completely disabled under Ravenwood
+        return 0;
+    }
+
+    private static void nativeSetAppTracingAllowed$ravenwood(boolean allowed) {
+        // Tracing currently completely disabled under Ravenwood
+    }
+
+    private static void nativeSetTracingEnabled$ravenwood(boolean allowed) {
+        // Tracing currently completely disabled under Ravenwood
+    }
+
     @FastNative
     private static native void nativeTraceCounter(long tag, String name, long value);
     @FastNative
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index c280d13..533946d 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -88,6 +88,7 @@
  * See {@link DevicePolicyManager#ACTION_PROVISION_MANAGED_PROFILE} for more on managed profiles.
  */
 @SystemService(Context.USER_SERVICE)
+@android.ravenwood.annotation.RavenwoodKeepPartialClass
 public class UserManager {
 
     private static final String TAG = "UserManager";
@@ -106,6 +107,21 @@
     /** Whether the device is in headless system user mode; null until cached. */
     private static Boolean sIsHeadlessSystemUser = null;
 
+    /** Maximum length of username.
+     * @hide
+     */
+    public static final int MAX_USER_NAME_LENGTH = 100;
+
+    /** Maximum length of user property String value.
+     * @hide
+     */
+    public static final int MAX_ACCOUNT_STRING_LENGTH = 500;
+
+    /** Maximum length of account options String values.
+     * @hide
+     */
+    public static final int MAX_ACCOUNT_OPTIONS_LENGTH = 1000;
+
     /**
      * User type representing a {@link UserHandle#USER_SYSTEM system} user that is a human user.
      * This type of user cannot be created; it can only pre-exist on first boot.
@@ -2906,6 +2922,7 @@
      * {@link UserManager#USER_TYPE_PROFILE_MANAGED managed profile}.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isUserTypeManagedProfile(@Nullable String userType) {
         return USER_TYPE_PROFILE_MANAGED.equals(userType);
     }
@@ -2914,6 +2931,7 @@
      * Returns whether the user type is a {@link UserManager#USER_TYPE_FULL_GUEST guest user}.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isUserTypeGuest(@Nullable String userType) {
         return USER_TYPE_FULL_GUEST.equals(userType);
     }
@@ -2923,6 +2941,7 @@
      * {@link UserManager#USER_TYPE_FULL_RESTRICTED restricted user}.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isUserTypeRestricted(@Nullable String userType) {
         return USER_TYPE_FULL_RESTRICTED.equals(userType);
     }
@@ -2931,6 +2950,7 @@
      * Returns whether the user type is a {@link UserManager#USER_TYPE_FULL_DEMO demo user}.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isUserTypeDemo(@Nullable String userType) {
         return USER_TYPE_FULL_DEMO.equals(userType);
     }
@@ -2939,6 +2959,7 @@
      * Returns whether the user type is a {@link UserManager#USER_TYPE_PROFILE_CLONE clone user}.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isUserTypeCloneProfile(@Nullable String userType) {
         return USER_TYPE_PROFILE_CLONE.equals(userType);
     }
@@ -2948,6 +2969,7 @@
      * {@link UserManager#USER_TYPE_PROFILE_COMMUNAL communal profile}.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isUserTypeCommunalProfile(@Nullable String userType) {
         return USER_TYPE_PROFILE_COMMUNAL.equals(userType);
     }
@@ -2958,6 +2980,7 @@
      *
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isUserTypePrivateProfile(@Nullable String userType) {
         return USER_TYPE_PROFILE_PRIVATE.equals(userType);
     }
@@ -4423,15 +4446,15 @@
      * This API should only be called if the current user is an {@link #isAdminUser() admin} user,
      * as otherwise the returned intent will not be able to create a user.
      *
-     * @param userName Optional name to assign to the user.
+     * @param userName Optional name to assign to the user. Character limit is 100.
      * @param accountName Optional account name that will be used by the setup wizard to initialize
-     *                    the user.
+     *                    the user. Character limit is 500.
      * @param accountType Optional account type for the account to be created. This is required
-     *                    if the account name is specified.
+     *                    if the account name is specified. Character limit is 500.
      * @param accountOptions Optional bundle of data to be passed in during account creation in the
      *                       new user via {@link AccountManager#addAccount(String, String, String[],
      *                       Bundle, android.app.Activity, android.accounts.AccountManagerCallback,
-     *                       Handler)}.
+     *                       Handler)}. Character limit is 1000.
      * @return An Intent that can be launched from an Activity.
      * @see #USER_CREATION_FAILED_NOT_PERMITTED
      * @see #USER_CREATION_FAILED_NO_MORE_USERS
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ecd6f22..11edcaf 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -444,6 +444,18 @@
             "android.settings.ACCESSIBILITY_DETAILS_SETTINGS";
 
     /**
+     * Activity Action: Show settings to allow configuration of an accessibility
+     * shortcut belonging to an accessibility feature or features.
+     * <p>
+     * Input: ":settings:show_fragment_args" must contain "targets" denoting the services to edit.
+     * <p>
+     * Output: Nothing.
+     * @hide
+     **/
+    public static final String ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS =
+            "android.settings.ACCESSIBILITY_SHORTCUT_SETTINGS";
+
+    /**
      * Activity Action: Show settings to allow configuration of accessibility color and motion.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you
@@ -4513,10 +4525,11 @@
         /** @hide */
         public static void adjustConfigurationForUser(ContentResolver cr, Configuration outConfig,
                 int userHandle, boolean updateSettingsIfEmpty) {
+            final float defaultFontScale = getDefaultFontScale(cr, userHandle);
             outConfig.fontScale = Settings.System.getFloatForUser(
-                    cr, FONT_SCALE, DEFAULT_FONT_SCALE, userHandle);
+                    cr, FONT_SCALE, defaultFontScale, userHandle);
             if (outConfig.fontScale < 0) {
-                outConfig.fontScale = DEFAULT_FONT_SCALE;
+                outConfig.fontScale = defaultFontScale;
             }
             outConfig.fontWeightAdjustment = Settings.Secure.getIntForUser(
                     cr, Settings.Secure.FONT_WEIGHT_ADJUSTMENT, DEFAULT_FONT_WEIGHT, userHandle);
@@ -4541,6 +4554,12 @@
             }
         }
 
+        private static float getDefaultFontScale(ContentResolver cr, int userHandle) {
+            return com.android.window.flags.Flags.configurableFontScaleDefault()
+                    ? Settings.System.getFloatForUser(cr, DEFAULT_DEVICE_FONT_SCALE,
+                    DEFAULT_FONT_SCALE, userHandle) : DEFAULT_FONT_SCALE;
+        }
+
         /**
          * @hide Erase the fields in the Configuration that should be applied
          * by the settings.
@@ -4907,6 +4926,15 @@
         public static final String FONT_SCALE = "font_scale";
 
         /**
+         * Default scaling factor for fonts for the specific device, float.
+         * The value is read from the {@link R.dimen.def_device_font_scale}
+         * configuration property.
+         *
+         * @hide
+         */
+        public static final String DEFAULT_DEVICE_FONT_SCALE = "device_font_scale";
+
+        /**
          * The serialized system locale value.
          *
          * Do not use this value directory.
@@ -6245,6 +6273,7 @@
             PRIVATE_SETTINGS.add(CAMERA_FLASH_NOTIFICATION);
             PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION);
             PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION_COLOR);
+            PRIVATE_SETTINGS.add(DEFAULT_DEVICE_FONT_SCALE);
         }
 
         /**
@@ -7881,6 +7910,17 @@
         public static final String ACCESSIBILITY_BOUNCE_KEYS = "accessibility_bounce_keys";
 
         /**
+         * Whether to enable slow keys for Physical Keyboard accessibility.
+         *
+         * If set to non-zero value, any key press on physical keyboard needs to be pressed and
+         * held for the provided threshold duration (in milliseconds) to be registered in the
+         * system.
+         *
+         * @hide
+         */
+        public static final String ACCESSIBILITY_SLOW_KEYS = "accessibility_slow_keys";
+
+        /**
          * Whether to enable sticky keys for Physical Keyboard accessibility.
          *
          * This is a boolean value that determines if Sticky keys feature is enabled.
@@ -12276,6 +12316,8 @@
             CLONE_TO_MANAGED_PROFILE.add(LOCATION_MODE);
             CLONE_TO_MANAGED_PROFILE.add(SHOW_IME_WITH_HARD_KEYBOARD);
             CLONE_TO_MANAGED_PROFILE.add(ACCESSIBILITY_BOUNCE_KEYS);
+            CLONE_TO_MANAGED_PROFILE.add(ACCESSIBILITY_SLOW_KEYS);
+            CLONE_TO_MANAGED_PROFILE.add(ACCESSIBILITY_STICKY_KEYS);
             CLONE_TO_MANAGED_PROFILE.add(NOTIFICATION_BUBBLES);
             CLONE_TO_MANAGED_PROFILE.add(NOTIFICATION_HISTORY_ENABLED);
         }
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index 7ea74d3..09ec933 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -28,6 +28,7 @@
 import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.app.Activity;
+import android.app.PendingIntent;
 import android.content.Intent;
 import android.content.IntentSender;
 import android.content.pm.ParceledListSlice;
@@ -116,6 +117,7 @@
     private final boolean mShowFillDialogIcon;
     private final boolean mShowSaveDialogIcon;
     private final @Nullable FieldClassification[] mDetectedFieldTypes;
+    private final @Nullable PendingIntent mDialogPendingIntent;
 
     /**
     * Creates a shollow copy of the provided FillResponse.
@@ -150,7 +152,8 @@
                 r.mServiceDisplayNameResourceId,
                 r.mShowFillDialogIcon,
                 r.mShowSaveDialogIcon,
-                r.mDetectedFieldTypes);
+                r.mDetectedFieldTypes,
+                r.mDialogPendingIntent);
     }
 
     private FillResponse(ParceledListSlice<Dataset> datasets, SaveInfo saveInfo, Bundle clientState,
@@ -163,7 +166,7 @@
             int[] cancelIds, boolean supportsInlineSuggestions, int iconResourceId,
             int serviceDisplayNameResourceId, boolean showFillDialogIcon,
             boolean showSaveDialogIcon,
-            FieldClassification[] detectedFieldTypes) {
+            FieldClassification[] detectedFieldTypes, PendingIntent dialogPendingIntent) {
         mDatasets = datasets;
         mSaveInfo = saveInfo;
         mClientState = clientState;
@@ -190,6 +193,7 @@
         mShowFillDialogIcon = showFillDialogIcon;
         mShowSaveDialogIcon = showSaveDialogIcon;
         mDetectedFieldTypes = detectedFieldTypes;
+        mDialogPendingIntent = dialogPendingIntent;
     }
 
     private FillResponse(@NonNull Builder builder) {
@@ -219,6 +223,7 @@
         mShowFillDialogIcon = builder.mShowFillDialogIcon;
         mShowSaveDialogIcon = builder.mShowSaveDialogIcon;
         mDetectedFieldTypes = builder.mDetectedFieldTypes;
+        mDialogPendingIntent = builder.mDialogPendingIntent;
     }
 
     /** @hide */
@@ -399,6 +404,7 @@
         private boolean mShowFillDialogIcon = true;
         private boolean mShowSaveDialogIcon = true;
         private FieldClassification[] mDetectedFieldTypes;
+        private PendingIntent mDialogPendingIntent;
 
         /**
          * Adds a new {@link FieldClassification} to this response, to
@@ -1079,6 +1085,24 @@
         }
 
         /**
+         * Sets credential dialog pending intent. Framework will use the intent to launch the
+         * selector UI. A replacement for previous fill bottom sheet.
+         *
+         * @throws IllegalStateException if {@link #build()} was already called.
+         * @throws NullPointerException if {@code pendingIntent} is {@code null}.
+         *
+         * @hide
+         */
+        @NonNull
+        public Builder setDialogPendingIntent(@NonNull PendingIntent pendingIntent) {
+            throwIfDestroyed();
+            Preconditions.checkNotNull(pendingIntent,
+                    "can't pass a null object to setDialogPendingIntent");
+            mDialogPendingIntent = pendingIntent;
+            return this;
+        }
+
+        /**
          * Builds a new {@link FillResponse} instance.
          *
          * @throws IllegalStateException if any of the following conditions occur:
@@ -1187,6 +1211,9 @@
         if (mAuthentication != null) {
             builder.append(", hasAuthentication");
         }
+        if (mDialogPendingIntent != null) {
+            builder.append(", hasDialogPendingIntent");
+        }
         if (mAuthenticationIds != null) {
             builder.append(", authenticationIds=").append(Arrays.toString(mAuthenticationIds));
         }
@@ -1232,6 +1259,7 @@
         parcel.writeParcelable(mInlineTooltipPresentation, flags);
         parcel.writeParcelable(mDialogPresentation, flags);
         parcel.writeParcelable(mDialogHeader, flags);
+        parcel.writeParcelable(mDialogPendingIntent, flags);
         parcel.writeParcelableArray(mFillDialogTriggerIds, flags);
         parcel.writeParcelable(mHeader, flags);
         parcel.writeParcelable(mFooter, flags);
@@ -1282,6 +1310,11 @@
             if (dialogHeader != null) {
                 builder.setDialogHeader(dialogHeader);
             }
+            final PendingIntent dialogPendingIntent = parcel.readParcelable(null,
+                    PendingIntent.class);
+            if (dialogPendingIntent != null) {
+                builder.setDialogPendingIntent(dialogPendingIntent);
+            }
             final AutofillId[] triggerIds = parcel.readParcelableArray(null, AutofillId.class);
             if (triggerIds != null) {
                 builder.setFillDialogTriggerIds(triggerIds);
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 9895551..9d19ef6 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -1053,7 +1053,7 @@
                 out);
 
         if (Flags.modesApi()) {
-            writeZenPolicyState(ALLOW_ATT_CHANNELS, policy.getPriorityChannels(), out);
+            writeZenPolicyState(ALLOW_ATT_CHANNELS, policy.getPriorityChannelsAllowed(), out);
         }
     }
 
@@ -1381,7 +1381,7 @@
         int state = defaultPolicy.state;
         if (Flags.modesApi()) {
             state = Policy.policyState(defaultPolicy.hasPriorityChannels(),
-                    ZenPolicy.stateToBoolean(zenPolicy.getPriorityChannels(),
+                    ZenPolicy.stateToBoolean(zenPolicy.getPriorityChannelsAllowed(),
                             DEFAULT_ALLOW_PRIORITY_CHANNELS));
         }
 
diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java
index d8318a6..786d768 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -570,7 +570,7 @@
      * with {@link NotificationChannel#canBypassDnd()} will be intercepted.
      */
     @FlaggedApi(Flags.FLAG_MODES_API)
-    public @State int getPriorityChannels() {
+    public @State int getPriorityChannelsAllowed() {
         switch (mAllowChannels) {
             case CHANNEL_POLICY_PRIORITY:
                 return STATE_ALLOW;
@@ -1529,7 +1529,7 @@
         proto.write(DNDPolicyProto.ALLOW_CONVERSATIONS_FROM, getPriorityConversationSenders());
 
         if (Flags.modesApi()) {
-            proto.write(DNDPolicyProto.ALLOW_CHANNELS, getPriorityChannels());
+            proto.write(DNDPolicyProto.ALLOW_CHANNELS, getPriorityChannelsAllowed());
         }
 
         proto.flush();
diff --git a/core/java/android/util/Singleton.java b/core/java/android/util/Singleton.java
index 92646b4..d27bef9 100644
--- a/core/java/android/util/Singleton.java
+++ b/core/java/android/util/Singleton.java
@@ -25,6 +25,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public abstract class Singleton<T> {
 
     @UnsupportedAppUsage
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 1908c64c..fbadef3 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -2079,6 +2079,7 @@
      *
      * @see Display#getSupportedModes()
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public static final class Mode implements Parcelable {
         /**
          * @hide
@@ -2467,6 +2468,7 @@
      * <p>You can get an instance for a given {@link Display} object with
      * {@link Display#getHdrCapabilities getHdrCapabilities()}.
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public static final class HdrCapabilities implements Parcelable {
         /**
          * Invalid luminance value.
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 981911e..5654bc1 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -51,6 +51,7 @@
  * Describes the characteristics of a particular logical display.
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class DisplayInfo implements Parcelable {
     /**
      * The surface flinger layer stack associated with this logical display.
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 1d81be1..d2c25cd 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -235,7 +235,7 @@
      */
     oneway void setWallpaperDisplayOffset(IBinder windowToken, int x, int y);
 
-    Bundle sendWallpaperCommand(IBinder window, String action, int x, int y,
+    oneway void sendWallpaperCommand(IBinder window, String action, int x, int y,
             int z, in Bundle extras, boolean sync);
 
     @UnsupportedAppUsage
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 86ab213..bc33d5e 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import static android.view.InsetsSourceProto.FRAME;
+import static android.view.InsetsSourceProto.TYPE;
 import static android.view.InsetsSourceProto.TYPE_NUMBER;
 import static android.view.InsetsSourceProto.VISIBLE;
 import static android.view.InsetsSourceProto.VISIBLE_FRAME;
@@ -442,6 +443,10 @@
      */
     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
+        if (!android.os.Flags.androidOsBuildVanillaIceCream()) {
+            // Deprecated since V.
+            proto.write(TYPE, WindowInsets.Type.toString(mType));
+        }
         mFrame.dumpDebug(proto, FRAME);
         if (mVisibleFrame != null) {
             mVisibleFrame.dumpDebug(proto, VISIBLE_FRAME);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 3c36227..7bc832e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -11991,7 +11991,7 @@
                         Runnable timeoutRunnable = () -> Log.e(mTag,
                                 "Failed to submit the sync transaction after 4s. Likely to ANR "
                                         + "soon");
-                        mHandler.postDelayed(timeoutRunnable, 4L * Build.HW_TIMEOUT_MULTIPLIER);
+                        mHandler.postDelayed(timeoutRunnable, 4000L * Build.HW_TIMEOUT_MULTIPLIER);
                         transaction.addTransactionCommittedListener(mSimpleExecutor,
                                 () -> mHandler.removeCallbacks(timeoutRunnable));
                         surfaceSyncGroup.addTransaction(transaction);
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index b95e459..c4d18c6 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -534,9 +534,8 @@
     }
 
     @Override
-    public android.os.Bundle sendWallpaperCommand(android.os.IBinder window,
+    public void sendWallpaperCommand(android.os.IBinder window,
             java.lang.String action, int x, int y, int z, android.os.Bundle extras, boolean sync) {
-        return null;
     }
 
     @Override
diff --git a/core/java/android/view/autofill/OWNERS b/core/java/android/view/autofill/OWNERS
index 37c6f5b..898947a 100644
--- a/core/java/android/view/autofill/OWNERS
+++ b/core/java/android/view/autofill/OWNERS
@@ -4,6 +4,7 @@
 haoranzhang@google.com
 skxu@google.com
 yunicorn@google.com
+reemabajwa@google.com
 
 # Bug component: 543785 = per-file *Augmented*
 per-file *Augmented* = wangqi@google.com
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index edfbea4..1de77f6 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -58,3 +58,11 @@
   bug: "319808237"
   is_fixed_read_only: true
 }
+
+flag {
+  name: "camera_compat_for_freeform"
+  namespace: "large_screen_experiences_app_compat"
+  description: "Whether to apply Camera Compat treatment to fixed-orientation apps in freeform windowing mode"
+  bug: "314952133"
+  is_fixed_read_only: true
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 2c5fbd7..f234637 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -38,14 +38,6 @@
 }
 
 flag {
-  name: "draw_magnifier_border_outside_wmlock"
-  namespace: "windowing_frontend"
-  description: "Avoid holding WM locks for a long time when executing lockCanvas"
-  bug: "316075123"
-  is_fixed_read_only: true
-}
-
-flag {
   name: "introduce_smoother_dimmer"
   namespace: "windowing_frontend"
   description: "Refactor dim to fix flickers"
diff --git a/core/java/com/android/internal/app/ConfirmUserCreationActivity.java b/core/java/com/android/internal/app/ConfirmUserCreationActivity.java
index 0a28997..b4e8749 100644
--- a/core/java/com/android/internal/app/ConfirmUserCreationActivity.java
+++ b/core/java/com/android/internal/app/ConfirmUserCreationActivity.java
@@ -116,6 +116,14 @@
         if (cantCreateUser) {
             setResult(UserManager.USER_CREATION_FAILED_NOT_PERMITTED);
             return null;
+        } else if (!(isUserPropertyWithinLimit(mUserName, UserManager.MAX_USER_NAME_LENGTH)
+                && isUserPropertyWithinLimit(mAccountName, UserManager.MAX_ACCOUNT_STRING_LENGTH)
+                && isUserPropertyWithinLimit(mAccountType, UserManager.MAX_ACCOUNT_STRING_LENGTH))
+                || (mAccountOptions != null && !mAccountOptions.isBundleContentsWithinLengthLimit(
+                UserManager.MAX_ACCOUNT_OPTIONS_LENGTH))) {
+            setResult(UserManager.USER_CREATION_FAILED_NOT_PERMITTED);
+            Log.i(TAG, "User properties must not exceed their character limits");
+            return null;
         } else if (cantCreateAnyMoreUsers) {
             setResult(UserManager.USER_CREATION_FAILED_NO_MORE_USERS);
             return null;
@@ -144,4 +152,8 @@
         }
         finish();
     }
+
+    private boolean isUserPropertyWithinLimit(String property, int limit) {
+        return property == null || property.length() <= limit;
+    }
 }
diff --git a/core/java/com/android/internal/display/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java
index 37aaa72..0068490 100644
--- a/core/java/com/android/internal/display/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java
@@ -47,6 +47,7 @@
  * (new) system for storing the brightness. It has methods to convert between the two and also
  * observes for when one of the settings is changed and syncs this with the other.
  */
+@android.ravenwood.annotation.RavenwoodKeepPartialClass
 public class BrightnessSynchronizer {
     private static final String TAG = "BrightnessSynchronizer";
 
@@ -282,6 +283,7 @@
      * @param b second float to compare
      * @return whether the two values are within a small enough tolerance value
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean floatEquals(float a, float b) {
         if (a == b) {
             return true;
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index 96740c5..7b3565b 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -121,10 +121,11 @@
     public static final int CUJ_PREDICTIVE_BACK_CROSS_TASK = 85;
     public static final int CUJ_PREDICTIVE_BACK_HOME = 86;
     public static final int CUJ_LAUNCHER_SEARCH_QSB_OPEN = 87;
+    public static final int CUJ_BACK_PANEL_ARROW = 88;
 
     // When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
     @VisibleForTesting
-    static final int LAST_CUJ = CUJ_LAUNCHER_SEARCH_QSB_OPEN;
+    static final int LAST_CUJ = CUJ_BACK_PANEL_ARROW;
 
     /** @hide */
     @IntDef({
@@ -207,6 +208,7 @@
             CUJ_PREDICTIVE_BACK_CROSS_TASK,
             CUJ_PREDICTIVE_BACK_HOME,
             CUJ_LAUNCHER_SEARCH_QSB_OPEN,
+            CUJ_BACK_PANEL_ARROW,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {
@@ -298,8 +300,8 @@
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_ACTIVITY;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_TASK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_TASK;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_HOME] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_HOME;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_SEARCH_QSB_OPEN] =
-            FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_SEARCH_QSB_OPEN;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_SEARCH_QSB_OPEN] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_SEARCH_QSB_OPEN;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_BACK_PANEL_ARROW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BACK_PANEL_ARROW;
     }
 
     private Cuj() {
@@ -474,6 +476,8 @@
                 return "PREDICTIVE_BACK_HOME";
             case CUJ_LAUNCHER_SEARCH_QSB_OPEN:
                 return "LAUNCHER_SEARCH_QSB_OPEN";
+            case CUJ_BACK_PANEL_ARROW:
+                return "BACK_PANEL_ARROW";
         }
         return "UNKNOWN";
     }
diff --git a/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java b/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
index f3f16a0..d9cac12 100644
--- a/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
+++ b/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
@@ -28,6 +28,7 @@
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.Trace;
+import android.util.Log;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.view.WindowCallbacks;
@@ -52,6 +53,7 @@
  * @hide
  */
 class InteractionMonitorDebugOverlay implements WindowCallbacks {
+    private static final String TAG = "InteractionMonitorDebug";
     private static final int REASON_STILL_RUNNING = -1000;
     private final Object mLock;
     // Sparse array where the key in the CUJ and the value is the session status, or null if
@@ -77,7 +79,7 @@
         mDebugPaint.setAntiAlias(false);
         mDebugFontMetrics = new Paint.FontMetrics();
         final Context context = ActivityThread.currentApplication();
-        mPackageName = context.getPackageName();
+        mPackageName = context == null ? "null" : context.getPackageName();
     }
 
     @UiThread
@@ -153,8 +155,14 @@
                           SparseArray<InteractionJankMonitor.RunningTracker> runningTrackers) {
         synchronized (mLock) {
             mRunningCujs.put(removedCuj, reason);
+            boolean isLoggable = Log.isLoggable(TAG, Log.DEBUG);
+            if (isLoggable) {
+                String cujName = Cuj.getNameOfCuj(removedCuj);
+                Log.d(TAG, cujName + (reason == REASON_END_NORMAL ? " ended" : " cancelled"));
+            }
             // If REASON_STILL_RUNNING is not in mRunningCujs, then all CUJs have ended
             if (mRunningCujs.indexOfValue(REASON_STILL_RUNNING) < 0) {
+                if (isLoggable) Log.d(TAG, "All CUJs ended");
                 mRunningCujs.clear();
                 dispose();
             } else {
@@ -186,6 +194,10 @@
 
     @UiThread
     void onTrackerAdded(@Cuj.CujType int addedCuj, InteractionJankMonitor.RunningTracker tracker) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            String cujName = Cuj.getNameOfCuj(addedCuj);
+            Log.d(TAG, cujName + " started");
+        }
         synchronized (mLock) {
             // Use REASON_STILL_RUNNING (not technically one of the '@Reasons') to indicate the CUJ
             // is still running
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index e58f4f0..88aa89a 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -34,6 +34,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class MetricsLogger {
     // define metric categories in frameworks/base/proto/src/metrics_constants.proto.
     // mirror changes in native version at system/core/libmetricslogger/metrics_logger.cpp
diff --git a/core/java/com/android/internal/logging/testing/FakeMetricsLogger.java b/core/java/com/android/internal/logging/testing/FakeMetricsLogger.java
index 6786427..df8bf31 100644
--- a/core/java/com/android/internal/logging/testing/FakeMetricsLogger.java
+++ b/core/java/com/android/internal/logging/testing/FakeMetricsLogger.java
@@ -12,6 +12,7 @@
  *
  * @hide.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class FakeMetricsLogger extends MetricsLogger {
     private Queue<LogMaker> logs = new LinkedList<>();
 
diff --git a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
index e303890..6787ddc 100644
--- a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
+++ b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
@@ -27,6 +27,7 @@
  *
  * @hide.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class UiEventLoggerFake implements UiEventLogger {
     /**
      * Immutable data class used to record fake log events.
diff --git a/core/java/com/android/internal/policy/SystemBarUtils.java b/core/java/com/android/internal/policy/SystemBarUtils.java
index 7a1ac07..efa3697 100644
--- a/core/java/com/android/internal/policy/SystemBarUtils.java
+++ b/core/java/com/android/internal/policy/SystemBarUtils.java
@@ -19,8 +19,9 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Insets;
-import android.util.RotationUtils;
+import android.view.Display;
 import android.view.DisplayCutout;
+import android.view.DisplayInfo;
 import android.view.Surface;
 
 import com.android.internal.R;
@@ -56,21 +57,21 @@
      */
     public static int getStatusBarHeightForRotation(
             Context context, @Surface.Rotation int targetRot) {
-        final int rotation = context.getDisplay().getRotation();
-        final DisplayCutout cutout = context.getDisplay().getCutout();
-
-        Insets insets = cutout == null ? Insets.NONE : Insets.of(cutout.getSafeInsets());
-        Insets waterfallInsets = cutout == null ? Insets.NONE : cutout.getWaterfallInsets();
-        // rotate insets to target rotation if needed.
-        if (rotation != targetRot) {
-            if (!insets.equals(Insets.NONE)) {
-                insets = RotationUtils.rotateInsets(
-                        insets, RotationUtils.deltaRotation(rotation, targetRot));
-            }
-            if (!waterfallInsets.equals(Insets.NONE)) {
-                waterfallInsets = RotationUtils.rotateInsets(
-                        waterfallInsets, RotationUtils.deltaRotation(rotation, targetRot));
-            }
+        final Display display = context.getDisplay();
+        final int rotation = display.getRotation();
+        final DisplayCutout cutout = display.getCutout();
+        DisplayInfo info = new DisplayInfo();
+        display.getDisplayInfo(info);
+        Insets insets;
+        Insets waterfallInsets;
+        if (cutout == null) {
+            insets = Insets.NONE;
+            waterfallInsets = Insets.NONE;
+        } else {
+            DisplayCutout rotated =
+                    cutout.getRotated(info.logicalWidth, info.logicalHeight, rotation, targetRot);
+            insets = Insets.of(rotated.getSafeInsets());
+            waterfallInsets = rotated.getWaterfallInsets();
         }
         final int defaultSize =
                 context.getResources().getDimensionPixelSize(R.dimen.status_bar_height_default);
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 42be784..a8d0d37 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -105,6 +105,9 @@
     private int mConversationIconTopPaddingExpandedGroup;
     private int mConversationIconTopPadding;
     private int mExpandedGroupMessagePadding;
+    // TODO (b/217799515) Currently, mConversationText shows the conversation title, the actual
+    //  conversation text is inside of mMessagingLinearLayout, which is misleading, we should rename
+    //  this to mConversationTitleView
     private TextView mConversationText;
     private View mConversationIconBadge;
     private CachingIconView mConversationIconBadgeBg;
@@ -125,6 +128,11 @@
     private int mNotificationBackgroundColor;
     private CharSequence mFallbackChatName;
     private CharSequence mFallbackGroupChatName;
+    //TODO (b/217799515) Currently, Notification.MessagingStyle, ConversationLayout, and
+    // HybridConversationNotificationView, each has their own definition of "ConversationTitle".
+    // What make things worse is that the term of "ConversationTitle" often confuses with
+    // "ConversationText".
+    // We need to unify them or differentiate the namings.
     private CharSequence mConversationTitle;
     private int mMessageSpacingStandard;
     private int mMessageSpacingGroup;
@@ -160,12 +168,12 @@
     }
 
     public ConversationLayout(@NonNull Context context, @Nullable AttributeSet attrs,
-            @AttrRes int defStyleAttr) {
+                              @AttrRes int defStyleAttr) {
         super(context, attrs, defStyleAttr);
     }
 
     public ConversationLayout(@NonNull Context context, @Nullable AttributeSet attrs,
-            @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+                              @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
     }
 
@@ -297,13 +305,17 @@
         mNameReplacement = nameReplacement;
     }
 
-    /** Sets this conversation as "important", adding some additional UI treatment. */
+    /**
+     * Sets this conversation as "important", adding some additional UI treatment.
+     */
     @RemotableViewMethod
     public void setIsImportantConversation(boolean isImportantConversation) {
         setIsImportantConversation(isImportantConversation, false);
     }
 
-    /** @hide **/
+    /**
+     * @hide
+     **/
     public void setIsImportantConversation(boolean isImportantConversation, boolean animate) {
         mImportantConversation = isImportantConversation;
         mImportanceRingView.setVisibility(isImportantConversation && mIcon.getVisibility() != GONE
@@ -386,6 +398,7 @@
 
     /**
      * Set conversation data
+     *
      * @param extras Bundle contains conversation data
      */
     @RemotableViewMethod(asyncImpl = "setDataAsync")
@@ -427,6 +440,7 @@
      * RemotableViewMethod's asyncImpl of {@link #setData(Bundle)}.
      * This should be called on a background thread, and returns a Runnable which is then must be
      * called on the main thread to complete the operation and set text.
+     *
      * @param extras Bundle contains conversation data
      * @hide
      */
@@ -449,6 +463,7 @@
 
     /**
      * enable/disable precomputed text usage
+     *
      * @hide
      */
     public void setPrecomputedTextEnabled(boolean precomputedTextEnabled) {
@@ -466,7 +481,9 @@
         mImageResolver = resolver;
     }
 
-    /** @hide */
+    /**
+     * @hide
+     */
     public void setUnreadCount(int unreadCount) {
         mExpandButton.setNumber(unreadCount);
     }
@@ -795,6 +812,10 @@
         mConversationTitle = conversationTitle != null ? conversationTitle.toString() : null;
     }
 
+    // TODO (b/217799515) getConversationTitle is not consistent with setConversationTitle
+    //  if you call getConversationTitle() immediately after setConversationTitle(), the result
+    //  will not correctly reflect the new change without calling updateConversationLayout, for
+    //  example.
     public CharSequence getConversationTitle() {
         return mConversationText.getText();
     }
@@ -914,7 +935,7 @@
     }
 
     private void createGroupViews(List<List<MessagingMessage>> groups,
-            List<Person> senders, boolean showSpinner) {
+                                  List<Person> senders, boolean showSpinner) {
         mGroups.clear();
         for (int groupIndex = 0; groupIndex < groups.size(); groupIndex++) {
             List<MessagingMessage> group = groups.get(groupIndex);
@@ -963,8 +984,8 @@
     }
 
     private void findGroups(List<MessagingMessage> historicMessages,
-            List<MessagingMessage> messages, List<List<MessagingMessage>> groups,
-            List<Person> senders) {
+                            List<MessagingMessage> messages, List<List<MessagingMessage>> groups,
+                            List<Person> senders) {
         CharSequence currentSenderKey = null;
         List<MessagingMessage> currentGroup = null;
         int histSize = historicMessages.size();
diff --git a/core/java/com/android/internal/widget/ImageFloatingTextView.java b/core/java/com/android/internal/widget/ImageFloatingTextView.java
index 0704cb8..5da6435 100644
--- a/core/java/com/android/internal/widget/ImageFloatingTextView.java
+++ b/core/java/com/android/internal/widget/ImageFloatingTextView.java
@@ -18,9 +18,11 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
+import android.os.Build;
 import android.os.Trace;
 import android.text.BoringLayout;
 import android.text.Layout;
+import android.text.PrecomputedText;
 import android.text.StaticLayout;
 import android.text.TextUtils;
 import android.text.method.TransformationMethod;
@@ -48,6 +50,10 @@
     private int mLayoutMaxLines = -1;
     private int mImageEndMargin;
 
+    private int mStaticLayoutCreationCountInOnMeasure = 0;
+
+    private static final boolean TRACE_ONMEASURE = Build.isDebuggable();
+
     public ImageFloatingTextView(Context context) {
         this(context, null);
     }
@@ -71,7 +77,10 @@
     protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
             Layout.Alignment alignment, boolean shouldEllipsize,
             TextUtils.TruncateAt effectiveEllipsize, boolean useSaved) {
-        Trace.beginSection("ImageFloatingTextView#makeSingleLayout");
+        if (TRACE_ONMEASURE) {
+            Trace.beginSection("ImageFloatingTextView#makeSingleLayout");
+            mStaticLayoutCreationCountInOnMeasure++;
+        }
         TransformationMethod transformationMethod = getTransformationMethod();
         CharSequence text = getText();
         if (transformationMethod != null) {
@@ -79,7 +88,7 @@
         }
         text = text == null ? "" : text;
         StaticLayout.Builder builder = StaticLayout.Builder.obtain(text, 0, text.length(),
-                getPaint(), wantWidth)
+                        getPaint(), wantWidth)
                 .setAlignment(alignment)
                 .setTextDirection(getTextDirectionHeuristic())
                 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
@@ -115,7 +124,10 @@
         }
 
         final StaticLayout result = builder.build();
-        Trace.endSection();
+        if (TRACE_ONMEASURE) {
+            trackMaxLines();
+            Trace.endSection();
+        }
         return result;
     }
 
@@ -141,7 +153,10 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        Trace.beginSection("ImageFloatingTextView#onMeasure");
+        if (TRACE_ONMEASURE) {
+            Trace.beginSection("ImageFloatingTextView#onMeasure");
+        }
+        mStaticLayoutCreationCountInOnMeasure = 0;
         int availableHeight = MeasureSpec.getSize(heightMeasureSpec) - mPaddingTop - mPaddingBottom;
         if (getLayout() != null && getLayout().getHeight() != availableHeight) {
             // We've been measured before and the new size is different than before, lets make sure
@@ -168,7 +183,12 @@
                 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
             }
         }
-        Trace.endSection();
+
+
+        if (TRACE_ONMEASURE) {
+            trackParameters();
+            Trace.endSection();
+        }
     }
 
     @Override
@@ -216,4 +236,37 @@
             requestLayout();
         }
     }
+
+    private void trackParameters() {
+        if (!TRACE_ONMEASURE) {
+            return;
+        }
+        Trace.setCounter("ImageFloatingView#staticLayoutCreationCount",
+                mStaticLayoutCreationCountInOnMeasure);
+        Trace.setCounter("ImageFloatingView#isPrecomputedText",
+                isTextAPrecomputedText());
+    }
+    /**
+     * @return 1 if {@link TextView#getText()} is PrecomputedText, else 0
+     */
+    private int isTextAPrecomputedText() {
+        final CharSequence text = getText();
+        if (text == null) {
+            return 0;
+        }
+
+        if (text instanceof PrecomputedText) {
+            return 1;
+        }
+
+        return 0;
+    }
+
+    private void trackMaxLines() {
+        if (!TRACE_ONMEASURE) {
+            return;
+        }
+
+        Trace.setCounter("ImageFloatingView#layoutMaxLines", mLayoutMaxLines);
+    }
 }
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 757978b..b5b3a48 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -333,11 +333,17 @@
 
     @UnsupportedAppUsage
     public LockPatternUtils(Context context) {
+        this(context, null);
+    }
+
+    @VisibleForTesting
+    public LockPatternUtils(Context context, ILockSettings lockSettings) {
         mContext = context;
         mContentResolver = context.getContentResolver();
 
         Looper looper = Looper.myLooper();
         mHandler = looper != null ? new Handler(looper) : null;
+        mLockSettingsService = lockSettings;
     }
 
     @UnsupportedAppUsage
diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java
index c06f5f7..e07acac 100644
--- a/core/java/com/android/internal/widget/MessagingLinearLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java
@@ -21,6 +21,8 @@
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
+import android.os.Build;
+import android.os.Trace;
 import android.util.AttributeSet;
 import android.view.RemotableViewMethod;
 import android.view.View;
@@ -45,6 +47,8 @@
 
     private int mMaxDisplayedLines = Integer.MAX_VALUE;
 
+    private static final boolean TRACE_ONMEASURE = Build.isDebuggable();
+
     public MessagingLinearLayout(Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
 
@@ -67,6 +71,10 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (TRACE_ONMEASURE) {
+            Trace.beginSection("MessagingLinearLayout#onMeasure");
+            trackMeasureSpecs(widthMeasureSpec, heightMeasureSpec);
+        }
         // This is essentially a bottom-up linear layout that only adds children that fit entirely
         // up to a maximum height.
         int targetHeight = MeasureSpec.getSize(heightMeasureSpec);
@@ -177,6 +185,9 @@
                 resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth),
                         widthMeasureSpec),
                 Math.max(getSuggestedMinimumHeight(), totalHeight));
+        if (TRACE_ONMEASURE) {
+            Trace.endSection();
+        }
     }
 
     @Override
@@ -240,6 +251,25 @@
         }
     }
 
+    private void trackMeasureSpecs(int widthMeasureSpec, int heightMeasureSpec) {
+        if (!TRACE_ONMEASURE) {
+            return;
+        }
+
+        final int availableWidth = MeasureSpec.getSize(widthMeasureSpec);
+        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        final int availableHeight = MeasureSpec.getSize(heightMeasureSpec);
+        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        Trace.setCounter("MessagingLinearLayout#onMeasure_widthMeasureSpecSize",
+                availableWidth);
+        Trace.setCounter("MessagingLinearLayout#onMeasure_widthMeasureSpecMode",
+                widthMode);
+        Trace.setCounter("MessagingLinearLayout#onMeasure_heightMeasureSpecSize",
+                availableHeight);
+        Trace.setCounter("MessagingLinearLayout#onMeasure_heightMeasureSpecMode",
+                heightMode);
+    }
+
     @Override
     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
         final LayoutParams lp = (LayoutParams) child.getLayoutParams();
diff --git a/core/java/com/android/internal/widget/PeopleHelper.java b/core/java/com/android/internal/widget/PeopleHelper.java
index 85cedc3..3f5b4a0 100644
--- a/core/java/com/android/internal/widget/PeopleHelper.java
+++ b/core/java/com/android/internal/widget/PeopleHelper.java
@@ -22,6 +22,8 @@
 import android.annotation.ColorInt;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.Person;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -222,6 +224,72 @@
     }
 
     /**
+     * A class that represents a map from unique sender names in the groups to the string 1- or
+     * 2-character prefix strings for the names. This class uses the String value of the
+     * CharSequence Names as the key.
+     */
+    public class NameToPrefixMap {
+        Map<String, String> mMap;
+        NameToPrefixMap(Map<String, String> map) {
+            this.mMap = map;
+        }
+
+        /**
+         * @param name the name
+         * @return the prefix of the given name
+         */
+        public String getPrefix(CharSequence name) {
+            return mMap.get(name.toString());
+        }
+    }
+
+    /**
+     * Same functionality as mapUniqueNamesToPrefix, but takes list-represented message groups as
+     * the input. This method is better when inflating MessagingGroup from the UI thread is not
+     * an option.
+     * @param groups message groups represented by lists. A message group is some consecutive
+     *               messages (>=3) from the same sender in a conversation.
+     */
+    public NameToPrefixMap mapUniqueNamesToPrefixWithGroupList(
+            List<List<Notification.MessagingStyle.Message>> groups) {
+        // Map of unique names to their prefix
+        ArrayMap<String, String> uniqueNames = new ArrayMap<>();
+        // Map of single-character string prefix to the only name which uses it, or null if multiple
+        ArrayMap<String, CharSequence> uniqueCharacters = new ArrayMap<>();
+        for (int i = 0; i < groups.size(); i++) {
+            List<Notification.MessagingStyle.Message> group = groups.get(i);
+            if (group.isEmpty()) continue;
+            Person sender = group.get(0).getSenderPerson();
+            if (sender == null) continue;
+            CharSequence senderName = sender.getName();
+            if (sender.getIcon() != null || TextUtils.isEmpty(senderName)) {
+                continue;
+            }
+            String senderNameString = senderName.toString();
+            if (!uniqueNames.containsKey(senderNameString)) {
+                String charPrefix = findNamePrefix(senderName, null);
+                if (charPrefix == null) {
+                    continue;
+                }
+                if (uniqueCharacters.containsKey(charPrefix)) {
+                    // this character was already used, lets make it more unique. We first need to
+                    // resolve the existing character if it exists
+                    CharSequence existingName = uniqueCharacters.get(charPrefix);
+                    if (existingName != null) {
+                        uniqueNames.put(existingName.toString(), findNameSplit(existingName));
+                        uniqueCharacters.put(charPrefix, null);
+                    }
+                    uniqueNames.put(senderNameString, findNameSplit(senderName));
+                } else {
+                    uniqueNames.put(senderNameString, charPrefix);
+                    uniqueCharacters.put(charPrefix, senderName);
+                }
+            }
+        }
+        return new NameToPrefixMap(uniqueNames);
+    }
+
+    /**
      * Update whether the groups can hide the sender if they are first
      * (happens only for 1:1 conversations where the given title matches the sender's name)
      */
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 6a640a5..d2e58bb 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -1165,12 +1165,11 @@
 
 static jlongArray android_os_Process_getRss(JNIEnv* env, jobject clazz, jint pid)
 {
-    // total, file, anon, swap
-    jlong rss[4] = {0, 0, 0, 0};
+    // total, file, anon, swap, shmem
+    jlong rss[5] = {0, 0, 0, 0, 0};
     std::string status_path =
             android::base::StringPrintf("/proc/%d/status", pid);
     UniqueFile file = MakeUniqueFile(status_path.c_str(), "re");
-
     char line[256];
     while (file != nullptr && fgets(line, sizeof(line), file.get())) {
         jlong v;
@@ -1182,17 +1181,18 @@
             rss[2] = v;
         } else if ( sscanf(line, "VmSwap: %" SCNd64 " kB", &v) == 1) {
             rss[3] = v;
+        } else if ( sscanf(line, "RssShmem: %" SCNd64 " kB", &v) == 1) {
+            rss[4] = v;
         }
     }
 
-    jlongArray rssArray = env->NewLongArray(4);
+    jlongArray rssArray = env->NewLongArray(5);
     if (rssArray == NULL) {
         jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
         return NULL;
     }
 
-    env->SetLongArrayRegion(rssArray, 0, 4, rss);
-
+    env->SetLongArrayRegion(rssArray, 0, 5, rss);
     return rssArray;
 }
 
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 25b2aaf..98f409a 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -343,7 +343,9 @@
                   const std::vector<SurfaceControlStats>& /*stats*/) {
         JNIEnv* env = getenv();
         // Adding a strong reference for java SyncFence
-        presentFence->incStrong(0);
+        if (presentFence) {
+            presentFence->incStrong(0);
+        }
 
         jobject stats =
                 env->NewObject(gTransactionStatsClassInfo.clazz, gTransactionStatsClassInfo.ctor,
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index b63021d..c92435f 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -464,6 +464,7 @@
     repeated .android.graphics.RectProto keep_clear_areas = 45;
     repeated .android.graphics.RectProto unrestricted_keep_clear_areas = 46;
     repeated .android.view.InsetsSourceProto mergedLocalInsetsSources = 47;
+    optional int32 requested_visible_types = 48;
 }
 
 message IdentifierProto {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 100259e..0e0af4d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6654,7 +6654,14 @@
 
     <!-- Allows the system to control the BiometricDialog (SystemUI). Reserved for the system. @hide -->
     <permission android:name="android.permission.MANAGE_BIOMETRIC_DIALOG"
-        android:protectionLevel="signature" />
+                android:protectionLevel="signature" />
+
+    <!-- Allows an application to set the BiometricDialog (SystemUI) logo .
+         <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" />
 
     <!-- Allows an application to control keyguard.  Only allowed for system processes.
         @hide -->
@@ -7065,6 +7072,7 @@
         android:protectionLevel="signature" />
 
     <!-- @SystemApi Allows an application to access the smartspace service as a client.
+     @FlaggedApi(android.app.smartspace.flags.Flags.FLAG_ACCESS_SMARTSPACE)
      @hide  <p>Not for use by third-party applications.</p> -->
     <permission android:name="android.permission.ACCESS_SMARTSPACE"
         android:protectionLevel="signature|privileged|development" />
@@ -7800,6 +7808,16 @@
     <permission android:name="android.permission.RUN_USER_INITIATED_JOBS"
                 android:protectionLevel="normal"/>
 
+    <!-- @FlaggedApi("android.app.job.backup_jobs_exemption")
+         Gives applications whose <b>primary use case</b> is to backup or sync content increased
+         job execution allowance in order to complete the related work. The jobs must have a valid
+         content URI trigger and network constraint set.
+         <p>This is a special access permission that can be revoked by the system or the user.
+         <p>Protection level: signature|privileged|appop
+     -->
+    <permission android:name="android.permission.RUN_BACKUP_JOBS"
+                android:protectionLevel="signature|privileged|appop"/>
+
     <!-- Allows an app access to the installer provided app metadata.
         @SystemApi
         @hide
@@ -8373,6 +8391,16 @@
             </intent-filter>
         </receiver>
 
+        <!-- Broadcast Receiver listens to sufficient verifier broadcast from Package Manager
+            when installing new SDK. Verification of SDK code during installation time is run
+            to determine compatibility with privacy sandbox restrictions. -->
+        <receiver android:name="com.android.server.sdksandbox.SdkSandboxVerifierReceiver"
+                 android:exported="false">
+            <intent-filter>
+                <action android:name="android.intent.action.PACKAGE_NEEDS_VERIFICATION"/>
+            </intent-filter>
+        </receiver>
+
         <service android:name="android.hardware.location.GeofenceHardwareService"
             android:permission="android.permission.LOCATION_HARDWARE"
             android:exported="false" />
@@ -8414,6 +8442,10 @@
                  android:permission="android.permission.BIND_JOB_SERVICE">
         </service>
 
+        <service android:name="com.android.server.selinux.SelinuxAuditLogsService"
+                 android:permission="android.permission.BIND_JOB_SERVICE">
+        </service>
+
         <service android:name="com.android.server.compos.IsolatedCompilationJobService"
                  android:permission="android.permission.BIND_JOB_SERVICE">
         </service>
diff --git a/core/res/res/color-night/notification_expand_button_state_tint.xml b/core/res/res/color-night/notification_expand_button_state_tint.xml
deleted file mode 100644
index a794d53..0000000
--- a/core/res/res/color-night/notification_expand_button_state_tint.xml
+++ /dev/null
@@ -1,21 +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.
-  -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_pressed="true" android:color="@android:color/system_on_surface_dark" android:alpha="0.06"/>
-    <item android:state_hovered="true" android:color="@android:color/system_on_surface_dark" android:alpha="0.03"/>
-    <item android:color="@android:color/system_on_surface_dark" android:alpha="0.00"/>
-</selector>
\ No newline at end of file
diff --git a/core/res/res/color/notification_expand_button_state_tint.xml b/core/res/res/color/notification_expand_button_state_tint.xml
index 67b2c25..5a8594f 100644
--- a/core/res/res/color/notification_expand_button_state_tint.xml
+++ b/core/res/res/color/notification_expand_button_state_tint.xml
@@ -14,8 +14,11 @@
   ~ limitations under the License.
   -->
 
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_pressed="true" android:color="@android:color/system_on_surface_light" android:alpha="0.12"/>
-    <item android:state_hovered="true" android:color="@android:color/system_on_surface_light" android:alpha="0.08"/>
-    <item android:color="@android:color/system_on_surface_light" android:alpha="0.00"/>
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <item android:state_pressed="true" android:color="?androidprv:attr/materialColorOnPrimaryFixed"
+          android:alpha="0.15"/>
+    <item android:state_hovered="true" android:color="?androidprv:attr/materialColorOnPrimaryFixed"
+          android:alpha="0.11"/>
+    <item android:color="@color/transparent" />
 </selector>
\ No newline at end of file
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 0d1a987..23c78fd 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -6611,7 +6611,7 @@
     </string-array>
 
     <!-- Whether or not the monitoring on the apps' background battery drain is enabled -->
-    <bool name="config_bg_current_drain_monitor_enabled">true</bool>
+    <bool name="config_bg_current_drain_monitor_enabled">false</bool>
 
     <!-- The threshold of the background current drain (in percentage) to the restricted
          standby bucket.
diff --git a/core/tests/InputMethodCoreTests/Android.bp b/core/tests/InputMethodCoreTests/Android.bp
new file mode 100644
index 0000000..ac64625
--- /dev/null
+++ b/core/tests/InputMethodCoreTests/Android.bp
@@ -0,0 +1,66 @@
+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"],
+}
+
+android_test {
+    name: "InputMethodCoreTests",
+
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+        "src/**/I*.aidl",
+    ],
+
+    dxflags: ["--core-library"],
+
+    static_libs: [
+        "collector-device-lib-platform",
+        "android-common",
+        "frameworks-core-util-lib",
+        "androidx.core_core",
+        "androidx.core_core-ktx",
+        "androidx.test.ext.junit",
+        "androidx.test.runner",
+        "androidx.test.rules",
+        "flag-junit",
+        "junit-params",
+        "kotlin-test",
+        "mockito-target-minus-junit4",
+        "platform-test-annotations",
+        "platform-compat-test-rules",
+        "truth",
+        "print-test-util-lib",
+        "testng",
+        "device-time-shell-utils",
+        "testables",
+        "flag-junit",
+    ],
+
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+        "android.test.mock",
+        "framework",
+        "ext",
+        "framework-res",
+    ],
+
+    sdk_version: "core_platform",
+    test_suites: [
+        "device-tests",
+        "automotive-tests",
+    ],
+
+    certificate: "platform",
+
+    resource_dirs: ["res"],
+
+    data: [
+        ":com.android.cts.helpers.aosp",
+    ],
+}
diff --git a/core/tests/InputMethodCoreTests/AndroidManifest.xml b/core/tests/InputMethodCoreTests/AndroidManifest.xml
new file mode 100644
index 0000000..8d00d0f
--- /dev/null
+++ b/core/tests/InputMethodCoreTests/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          android:installLocation="internalOnly"
+          package="com.android.frameworks.inputmethodcoretests"
+          android:sharedUserId="com.android.uid.test">
+
+    <application
+        android:supportsRtl="true"
+        android:enableOnBackInvokedCallback="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+            android:targetPackage="com.android.frameworks.inputmethodcoretests"
+            android:label="InputMethod Core Tests" />
+</manifest>
diff --git a/core/tests/InputMethodCoreTests/AndroidTest.xml b/core/tests/InputMethodCoreTests/AndroidTest.xml
new file mode 100644
index 0000000..fa585d8
--- /dev/null
+++ b/core/tests/InputMethodCoreTests/AndroidTest.xml
@@ -0,0 +1,38 @@
+<?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.
+-->
+<configuration description="Runs InputMethod Core Tests.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-instrumentation" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="InputMethodCoreTests.apk" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <!-- TODO(b/254155965): Design a mechanism to finally remove this command. -->
+        <option name="run-command" value="settings put global device_config_sync_disabled 0" />
+    </target_preparer>
+
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DeviceInteractionHelperInstaller" />
+
+    <option name="test-tag" value="InputMethodCoreTests" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.frameworks.inputmethodcoretests" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+</configuration>
diff --git a/core/tests/InputMethodCoreTests/OWNERS b/core/tests/InputMethodCoreTests/OWNERS
new file mode 100644
index 0000000..5deb2ce
--- /dev/null
+++ b/core/tests/InputMethodCoreTests/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/view/inputmethod/OWNERS
diff --git a/core/tests/coretests/res/xml/ime_meta.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta.xml
similarity index 100%
rename from core/tests/coretests/res/xml/ime_meta.xml
rename to core/tests/InputMethodCoreTests/res/xml/ime_meta.xml
diff --git a/core/tests/coretests/res/xml/ime_meta_inline_suggestions.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta_inline_suggestions.xml
similarity index 100%
rename from core/tests/coretests/res/xml/ime_meta_inline_suggestions.xml
rename to core/tests/InputMethodCoreTests/res/xml/ime_meta_inline_suggestions.xml
diff --git a/core/tests/coretests/res/xml/ime_meta_inline_suggestions_with_touch_exploration.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta_inline_suggestions_with_touch_exploration.xml
similarity index 100%
rename from core/tests/coretests/res/xml/ime_meta_inline_suggestions_with_touch_exploration.xml
rename to core/tests/InputMethodCoreTests/res/xml/ime_meta_inline_suggestions_with_touch_exploration.xml
diff --git a/core/tests/coretests/res/xml/ime_meta_sw_next.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta_sw_next.xml
similarity index 100%
rename from core/tests/coretests/res/xml/ime_meta_sw_next.xml
rename to core/tests/InputMethodCoreTests/res/xml/ime_meta_sw_next.xml
diff --git a/core/tests/coretests/res/xml/ime_meta_virtual_device_only.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta_virtual_device_only.xml
similarity index 100%
rename from core/tests/coretests/res/xml/ime_meta_virtual_device_only.xml
rename to core/tests/InputMethodCoreTests/res/xml/ime_meta_virtual_device_only.xml
diff --git a/core/tests/coretests/res/xml/ime_meta_vr_only.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta_vr_only.xml
similarity index 100%
rename from core/tests/coretests/res/xml/ime_meta_vr_only.xml
rename to core/tests/InputMethodCoreTests/res/xml/ime_meta_vr_only.xml
diff --git a/core/tests/coretests/src/android/view/inputmethod/BaseInputConnectionTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/BaseInputConnectionTest.java
similarity index 100%
rename from core/tests/coretests/src/android/view/inputmethod/BaseInputConnectionTest.java
rename to core/tests/InputMethodCoreTests/src/android/view/inputmethod/BaseInputConnectionTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/CursorAnchorInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/CursorAnchorInfoTest.java
similarity index 100%
rename from core/tests/coretests/src/android/view/inputmethod/CursorAnchorInfoTest.java
rename to core/tests/InputMethodCoreTests/src/android/view/inputmethod/CursorAnchorInfoTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/DeleteRangeGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/DeleteRangeGestureTest.java
similarity index 100%
rename from core/tests/coretests/src/android/view/inputmethod/DeleteRangeGestureTest.java
rename to core/tests/InputMethodCoreTests/src/android/view/inputmethod/DeleteRangeGestureTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java
similarity index 100%
rename from core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
rename to core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
similarity index 98%
rename from core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java
rename to core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
index 909af7b..a3f537e 100644
--- a/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
@@ -32,7 +32,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.frameworks.coretests.R;
+import com.android.frameworks.inputmethodcoretests.R;
 
 import org.junit.Rule;
 import org.junit.Test;
diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodManagerTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodManagerTest.java
similarity index 100%
rename from core/tests/coretests/src/android/view/inputmethod/InputMethodManagerTest.java
rename to core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodManagerTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java
similarity index 100%
rename from core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java
rename to core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeTest.java
similarity index 100%
rename from core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeTest.java
rename to core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/InsertGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertGestureTest.java
similarity index 100%
rename from core/tests/coretests/src/android/view/inputmethod/InsertGestureTest.java
rename to core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertGestureTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/InsertModeGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertModeGestureTest.java
similarity index 100%
rename from core/tests/coretests/src/android/view/inputmethod/InsertModeGestureTest.java
rename to core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertModeGestureTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java
similarity index 100%
rename from core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java
rename to core/tests/InputMethodCoreTests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/SelectGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectGestureTest.java
similarity index 100%
rename from core/tests/coretests/src/android/view/inputmethod/SelectGestureTest.java
rename to core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectGestureTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/SelectRangeGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectRangeGestureTest.java
similarity index 100%
rename from core/tests/coretests/src/android/view/inputmethod/SelectRangeGestureTest.java
rename to core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectRangeGestureTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/SparseRectFArrayTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SparseRectFArrayTest.java
similarity index 100%
rename from core/tests/coretests/src/android/view/inputmethod/SparseRectFArrayTest.java
rename to core/tests/InputMethodCoreTests/src/android/view/inputmethod/SparseRectFArrayTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/SurroundingTextTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SurroundingTextTest.java
similarity index 100%
rename from core/tests/coretests/src/android/view/inputmethod/SurroundingTextTest.java
rename to core/tests/InputMethodCoreTests/src/android/view/inputmethod/SurroundingTextTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/TextAppearanceInfoTest.java
similarity index 100%
rename from core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java
rename to core/tests/InputMethodCoreTests/src/android/view/inputmethod/TextAppearanceInfoTest.java
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/CompletableFutureUtilTest.kt b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/CompletableFutureUtilTest.kt
similarity index 100%
rename from core/tests/coretests/src/com/android/internal/inputmethod/CompletableFutureUtilTest.kt
rename to core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/CompletableFutureUtilTest.kt
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java
similarity index 100%
rename from core/tests/coretests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java
rename to core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodDebugTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodDebugTest.java
similarity index 100%
rename from core/tests/coretests/src/com/android/internal/inputmethod/InputMethodDebugTest.java
rename to core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodDebugTest.java
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java
similarity index 100%
rename from core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java
rename to core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java
similarity index 100%
rename from core/tests/coretests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java
rename to core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index d1a90ae..f476799 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -209,11 +209,15 @@
         "testng",
     ],
     srcs: [
+        "src/android/content/pm/PackageManagerTest.java",
+        "src/android/content/pm/UserInfoTest.java",
         "src/android/database/CursorWindowTest.java",
         "src/android/os/**/*.java",
+        "src/android/telephony/PinResultTest.java",
         "src/android/util/**/*.java",
+        "src/android/view/DisplayInfoTest.java",
+        "src/com/android/internal/logging/**/*.java",
         "src/com/android/internal/os/**/*.java",
-        "src/com/android/internal/os/LongArrayMultiStateCounterTest.java",
         "src/com/android/internal/util/**/*.java",
         "src/com/android/internal/power/EnergyConsumerStatsTest.java",
 
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTest.java b/core/tests/coretests/src/android/content/pm/PackageManagerTest.java
new file mode 100644
index 0000000..20421d1
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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.content.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class PackageManagerTest {
+    @Test
+    public void testPackageInfoFlags() throws Exception {
+        assertThat(PackageManager.PackageInfoFlags.of(42L).getValue()).isEqualTo(42L);
+    }
+
+    @Test
+    public void testApplicationInfoFlags() throws Exception {
+        assertThat(PackageManager.ApplicationInfoFlags.of(42L).getValue()).isEqualTo(42L);
+    }
+
+    @Test
+    public void testComponentInfoFlags() throws Exception {
+        assertThat(PackageManager.ComponentInfoFlags.of(42L).getValue()).isEqualTo(42L);
+    }
+
+    @Test
+    public void testResolveInfoFlags() throws Exception {
+        assertThat(PackageManager.ResolveInfoFlags.of(42L).getValue()).isEqualTo(42L);
+    }
+}
diff --git a/core/tests/coretests/src/android/content/pm/UserInfoTest.java b/core/tests/coretests/src/android/content/pm/UserInfoTest.java
new file mode 100644
index 0000000..af36dbb
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/UserInfoTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.UserHandle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class UserInfoTest {
+    @Test
+    public void testSimple() throws Exception {
+        final UserInfo ui = new UserInfo(10, "Test", UserInfo.FLAG_GUEST);
+
+        assertThat(ui.getUserHandle()).isEqualTo(UserHandle.of(10));
+        assertThat(ui.name).isEqualTo("Test");
+
+        // Derived based on userType field
+        assertThat(ui.isManagedProfile()).isEqualTo(false);
+        assertThat(ui.isGuest()).isEqualTo(true);
+        assertThat(ui.isRestricted()).isEqualTo(false);
+        assertThat(ui.isDemo()).isEqualTo(false);
+        assertThat(ui.isCloneProfile()).isEqualTo(false);
+        assertThat(ui.isCommunalProfile()).isEqualTo(false);
+        assertThat(ui.isPrivateProfile()).isEqualTo(false);
+
+        // Derived based on flags field
+        assertThat(ui.isPrimary()).isEqualTo(false);
+        assertThat(ui.isAdmin()).isEqualTo(false);
+        assertThat(ui.isProfile()).isEqualTo(false);
+        assertThat(ui.isEnabled()).isEqualTo(true);
+        assertThat(ui.isQuietModeEnabled()).isEqualTo(false);
+        assertThat(ui.isEphemeral()).isEqualTo(false);
+        assertThat(ui.isForTesting()).isEqualTo(false);
+        assertThat(ui.isInitialized()).isEqualTo(false);
+        assertThat(ui.isFull()).isEqualTo(false);
+        assertThat(ui.isMain()).isEqualTo(false);
+
+        // Derived dynamically
+        assertThat(ui.canHaveProfile()).isEqualTo(false);
+    }
+
+    @Test
+    public void testDebug() throws Exception {
+        final UserInfo ui = new UserInfo(10, "Test", UserInfo.FLAG_GUEST);
+
+        assertThat(ui.toString()).isNotEmpty();
+        assertThat(ui.toFullString()).isNotEmpty();
+    }
+}
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
index e32a57b..a2a5433 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
@@ -145,7 +145,6 @@
     fun unnecessaryFontScalesReturnsNull() {
         assertThat(FontScaleConverterFactory.forScale(0F)).isNull()
         assertThat(FontScaleConverterFactory.forScale(1F)).isNull()
-        assertThat(FontScaleConverterFactory.forScale(1.1F)).isNull()
         assertThat(FontScaleConverterFactory.forScale(0.85F)).isNull()
     }
 
@@ -176,7 +175,7 @@
         assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(-1f)).isFalse()
         assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(0.85f)).isFalse()
         assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.02f)).isFalse()
-        assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.10f)).isFalse()
+        assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.10f)).isTrue()
         assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.15f)).isTrue()
         assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.1499999f))
                 .isTrue()
diff --git a/core/tests/coretests/src/android/os/BuildTest.java b/core/tests/coretests/src/android/os/BuildTest.java
index 2d3e123..2a718ff 100644
--- a/core/tests/coretests/src/android/os/BuildTest.java
+++ b/core/tests/coretests/src/android/os/BuildTest.java
@@ -20,7 +20,6 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
-import android.platform.test.annotations.IgnoreUnderRavenwood;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.platform.test.ravenwood.RavenwoodRule;
 
@@ -71,7 +70,6 @@
      */
     @Test
     @SmallTest
-    @IgnoreUnderRavenwood(blockedBy = Build.class)
     public void testBuildFields() throws Exception {
         assertNotEmpty("ID", Build.ID);
         assertNotEmpty("DISPLAY", Build.DISPLAY);
diff --git a/core/tests/coretests/src/android/os/TraceTest.java b/core/tests/coretests/src/android/os/TraceTest.java
index 593833ec..b2c005f 100644
--- a/core/tests/coretests/src/android/os/TraceTest.java
+++ b/core/tests/coretests/src/android/os/TraceTest.java
@@ -34,7 +34,6 @@
  * while tracing on the emulator and then run traceview to view the trace.
  */
 @RunWith(AndroidJUnit4.class)
-@IgnoreUnderRavenwood(blockedBy = Trace.class)
 public class TraceTest {
     private static final String TAG = "TraceTest";
 
@@ -46,7 +45,51 @@
     private int gMethodCalls = 0;
 
     @Test
+    public void testEnableDisable() {
+        // Currently only verifying that we can invoke without crashing
+        Trace.setTracingEnabled(true, 0);
+        Trace.setTracingEnabled(false, 0);
+
+        Trace.setAppTracingAllowed(true);
+        Trace.setAppTracingAllowed(false);
+    }
+
+    @Test
+    public void testBeginEnd() {
+        // Currently only verifying that we can invoke without crashing
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG);
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+        Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, 42);
+        Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, 42);
+
+        Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, TAG, 42);
+        Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, 42);
+
+        Trace.beginSection(TAG);
+        Trace.endSection();
+
+        Trace.beginAsyncSection(TAG, 42);
+        Trace.endAsyncSection(TAG, 42);
+    }
+
+    @Test
+    public void testCounter() {
+        // Currently only verifying that we can invoke without crashing
+        Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, 42);
+        Trace.setCounter(TAG, 42);
+    }
+
+    @Test
+    public void testInstant() {
+        // Currently only verifying that we can invoke without crashing
+        Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG);
+        Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, TAG);
+    }
+
+    @Test
     public void testNullStrings() {
+        // Currently only verifying that we can invoke without crashing
         Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER, null, 42);
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, null);
 
@@ -62,6 +105,7 @@
 
     @Test
     @SmallTest
+    @IgnoreUnderRavenwood(blockedBy = Debug.class)
     public void testNativeTracingFromJava()
     {
         long start = System.currentTimeMillis();
@@ -82,6 +126,7 @@
     
     // This should not run in the automated suite.
     @Suppress
+    @IgnoreUnderRavenwood(blockedBy = Debug.class)
     public void disableTestNativeTracingFromC()
     {
         long start = System.currentTimeMillis();
@@ -97,6 +142,7 @@
     @Test
     @LargeTest
     @Suppress  // Failing.
+    @IgnoreUnderRavenwood(blockedBy = Debug.class)
     public void testMethodTracing()
     {
         long start = System.currentTimeMillis();
diff --git a/core/tests/coretests/src/android/telephony/PinResultTest.java b/core/tests/coretests/src/android/telephony/PinResultTest.java
new file mode 100644
index 0000000..c260807
--- /dev/null
+++ b/core/tests/coretests/src/android/telephony/PinResultTest.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class PinResultTest {
+    @Test
+    public void testSimple() throws Exception {
+        final PinResult res = new PinResult(PinResult.PIN_RESULT_TYPE_SUCCESS, 5);
+        assertThat(res.getResult()).isEqualTo(PinResult.PIN_RESULT_TYPE_SUCCESS);
+        assertThat(res.getAttemptsRemaining()).isEqualTo(5);
+    }
+}
diff --git a/core/tests/coretests/src/android/util/SingletonTest.java b/core/tests/coretests/src/android/util/SingletonTest.java
new file mode 100644
index 0000000..8c5a963
--- /dev/null
+++ b/core/tests/coretests/src/android/util/SingletonTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.util;
+
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SingletonTest {
+    @Test
+    public void testSimple() throws Exception {
+        final Singleton<Object> singleton = new Singleton<>() {
+            @Override
+            protected Object create() {
+                return new Object();
+            }
+        };
+
+        final Object first = singleton.get();
+        final Object second = singleton.get();
+        assertTrue(first == second);
+    }
+}
diff --git a/core/tests/coretests/src/android/view/DisplayInfoTest.java b/core/tests/coretests/src/android/view/DisplayInfoTest.java
index 803d38c..4c5b7e5 100644
--- a/core/tests/coretests/src/android/view/DisplayInfoTest.java
+++ b/core/tests/coretests/src/android/view/DisplayInfoTest.java
@@ -21,9 +21,12 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import android.platform.test.ravenwood.RavenwoodRule;
+
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -32,6 +35,9 @@
 public class DisplayInfoTest {
     private static final float FLOAT_EQUAL_DELTA = 0.0001f;
 
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     @Test
     public void testDefaultDisplayInfosAreEqual() {
         DisplayInfo displayInfo1 = new DisplayInfo();
diff --git a/core/tests/coretests/src/com/android/internal/logging/MetricsLoggerTest.java b/core/tests/coretests/src/com/android/internal/logging/MetricsLoggerTest.java
new file mode 100644
index 0000000..7054cc0
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/logging/MetricsLoggerTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.internal.logging;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.metrics.LogMaker;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.logging.testing.FakeMetricsLogger;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MetricsLoggerTest {
+    private FakeMetricsLogger mLogger;
+
+    private static final int TEST_ACTION = 42;
+
+    @Before
+    public void setUp() throws Exception {
+        mLogger = new FakeMetricsLogger();
+    }
+
+    @Test
+    public void testEmpty() throws Exception {
+        assertThat(mLogger.getLogs().size()).isEqualTo(0);
+    }
+
+    @Test
+    public void testAction() throws Exception {
+        mLogger.action(TEST_ACTION);
+        assertThat(mLogger.getLogs().size()).isEqualTo(1);
+        final LogMaker event = mLogger.getLogs().peek();
+        assertThat(event.getType()).isEqualTo(MetricsProto.MetricsEvent.TYPE_ACTION);
+        assertThat(event.getCategory()).isEqualTo(TEST_ACTION);
+    }
+
+    @Test
+    public void testVisible() throws Exception {
+        // Limited testing to confirm we don't crash
+        mLogger.visible(TEST_ACTION);
+        mLogger.hidden(TEST_ACTION);
+        mLogger.visibility(TEST_ACTION, true);
+        mLogger.visibility(TEST_ACTION, false);
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/logging/UiEventLoggerTest.java b/core/tests/coretests/src/com/android/internal/logging/UiEventLoggerTest.java
new file mode 100644
index 0000000..7840f71
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/logging/UiEventLoggerTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.internal.logging;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.logging.testing.UiEventLoggerFake;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class UiEventLoggerTest {
+    private UiEventLoggerFake mLogger;
+
+    private static final int TEST_EVENT_ID = 42;
+    private static final int TEST_INSTANCE_ID = 21;
+
+    private enum MyUiEventEnum implements UiEventLogger.UiEventEnum {
+        @UiEvent(doc = "Example event")
+        TEST_EVENT(TEST_EVENT_ID);
+
+        private final int mId;
+
+        MyUiEventEnum(int id) {
+            mId = id;
+        }
+
+        @Override
+        public int getId() {
+            return mId;
+        }
+    }
+
+    private InstanceId TEST_INSTANCE = InstanceId.fakeInstanceId(TEST_INSTANCE_ID);
+
+    @Before
+    public void setUp() throws Exception {
+        mLogger = new UiEventLoggerFake();
+    }
+
+    @Test
+    public void testEmpty() throws Exception {
+        assertThat(mLogger.numLogs()).isEqualTo(0);
+    }
+
+    @Test
+    public void testSimple() throws Exception {
+        mLogger.log(MyUiEventEnum.TEST_EVENT);
+        assertThat(mLogger.numLogs()).isEqualTo(1);
+        assertThat(mLogger.eventId(0)).isEqualTo(TEST_EVENT_ID);
+    }
+
+    @Test
+    public void testWithInstance() throws Exception {
+        mLogger.log(MyUiEventEnum.TEST_EVENT, TEST_INSTANCE);
+        assertThat(mLogger.numLogs()).isEqualTo(1);
+        assertThat(mLogger.eventId(0)).isEqualTo(TEST_EVENT_ID);
+        assertThat(mLogger.get(0).instanceId.getId()).isEqualTo(TEST_INSTANCE_ID);
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
index 1a668f7..b90480a 100644
--- a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
@@ -16,20 +16,279 @@
 
 package com.android.internal.widget;
 
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
+import android.app.trust.TrustManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.UserInfo;
+import android.os.Looper;
+import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.UserManager;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.util.test.FakeSettingsProvider;
+
+import com.google.android.collect.Lists;
+
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@IgnoreUnderRavenwood(blockedBy = LockPatternUtils.class)
 public class LockPatternUtilsTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+    private ILockSettings mLockSettings;
+    private static final int USER_ID = 1;
+    private static final int DEMO_USER_ID = 5;
+
+    private LockPatternUtils mLockPatternUtils;
+
+    private void configureTest(boolean isSecure, boolean isDemoUser, int deviceDemoMode)
+            throws Exception {
+        mLockSettings = mock(ILockSettings.class);
+        final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+
+        final MockContentResolver cr = new MockContentResolver(context);
+        cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+        when(context.getContentResolver()).thenReturn(cr);
+        Settings.Global.putInt(cr, Settings.Global.DEVICE_DEMO_MODE, deviceDemoMode);
+
+        when(mLockSettings.getCredentialType(DEMO_USER_ID)).thenReturn(
+                isSecure ? LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
+                         : LockPatternUtils.CREDENTIAL_TYPE_NONE);
+        when(mLockSettings.getLong("lockscreen.password_type", PASSWORD_QUALITY_UNSPECIFIED,
+                DEMO_USER_ID)).thenReturn((long) PASSWORD_QUALITY_MANAGED);
+        when(mLockSettings.hasSecureLockScreen()).thenReturn(true);
+        mLockPatternUtils = new LockPatternUtils(context, mLockSettings);
+
+        final UserInfo userInfo = mock(UserInfo.class);
+        when(userInfo.isDemo()).thenReturn(isDemoUser);
+        final UserManager um = mock(UserManager.class);
+        when(um.getUserInfo(DEMO_USER_ID)).thenReturn(userInfo);
+        when(context.getSystemService(Context.USER_SERVICE)).thenReturn(um);
+    }
+
+    @Test
+    public void isUserInLockDown() throws Exception {
+        configureTest(true, false, 2);
+
+        // GIVEN strong auth not required
+        when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(STRONG_AUTH_NOT_REQUIRED);
+
+        // THEN user isn't in lockdown
+        assertFalse(mLockPatternUtils.isUserInLockdown(USER_ID));
+
+        // GIVEN lockdown
+        when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(
+                STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+
+        // THEN user is in lockdown
+        assertTrue(mLockPatternUtils.isUserInLockdown(USER_ID));
+
+        // GIVEN lockdown and lockout
+        when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(
+                STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN | STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
+
+        // THEN user is in lockdown
+        assertTrue(mLockPatternUtils.isUserInLockdown(USER_ID));
+    }
+
+    @Test
+    public void isLockScreenDisabled_isDemoUser_true() throws Exception {
+        configureTest(false, true, 2);
+        assertTrue(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
+    }
+
+    @Test
+    public void isLockScreenDisabled_isSecureAndDemoUser_false() throws Exception {
+        configureTest(true, true, 2);
+        assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
+    }
+
+    @Test
+    public void isLockScreenDisabled_isNotDemoUser_false() throws Exception {
+        configureTest(false, false, 2);
+        assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
+    }
+
+    @Test
+    public void isLockScreenDisabled_isNotInDemoMode_false() throws Exception {
+        configureTest(false, true, 0);
+        assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
+    }
+
+    @Test
+    public void testAddWeakEscrowToken() throws RemoteException {
+        ILockSettings ils = createTestLockSettings();
+        byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8);
+        int testUserId = 10;
+        IWeakEscrowTokenActivatedListener listener = createWeakEscrowTokenListener();
+        mLockPatternUtils.addWeakEscrowToken(testToken, testUserId, listener);
+        verify(ils).addWeakEscrowToken(eq(testToken), eq(testUserId), eq(listener));
+    }
+
+    @Test
+    public void testRegisterWeakEscrowTokenRemovedListener() throws RemoteException {
+        ILockSettings ils = createTestLockSettings();
+        IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener();
+        mLockPatternUtils.registerWeakEscrowTokenRemovedListener(testListener);
+        verify(ils).registerWeakEscrowTokenRemovedListener(eq(testListener));
+    }
+
+    @Test
+    public void testUnregisterWeakEscrowTokenRemovedListener() throws RemoteException {
+        ILockSettings ils = createTestLockSettings();
+        IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener();
+        mLockPatternUtils.unregisterWeakEscrowTokenRemovedListener(testListener);
+        verify(ils).unregisterWeakEscrowTokenRemovedListener(eq(testListener));
+    }
+
+    @Test
+    public void testRemoveAutoEscrowToken() throws RemoteException {
+        ILockSettings ils = createTestLockSettings();
+        int testUserId = 10;
+        long testHandle = 100L;
+        mLockPatternUtils.removeWeakEscrowToken(testHandle, testUserId);
+        verify(ils).removeWeakEscrowToken(eq(testHandle), eq(testUserId));
+    }
+
+    @Test
+    public void testIsAutoEscrowTokenActive() throws RemoteException {
+        ILockSettings ils = createTestLockSettings();
+        int testUserId = 10;
+        long testHandle = 100L;
+        mLockPatternUtils.isWeakEscrowTokenActive(testHandle, testUserId);
+        verify(ils).isWeakEscrowTokenActive(eq(testHandle), eq(testUserId));
+    }
+
+    @Test
+    public void testIsAutoEscrowTokenValid() throws RemoteException {
+        ILockSettings ils = createTestLockSettings();
+        int testUserId = 10;
+        byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8);
+        long testHandle = 100L;
+        mLockPatternUtils.isWeakEscrowTokenValid(testHandle, testToken, testUserId);
+        verify(ils).isWeakEscrowTokenValid(eq(testHandle), eq(testToken), eq(testUserId));
+    }
+
+    @Test
+    public void testSetEnabledTrustAgents() throws RemoteException {
+        int testUserId = 10;
+        ILockSettings ils = createTestLockSettings();
+        ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
+        doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt());
+        List<ComponentName> enabledTrustAgents = Lists.newArrayList(
+                ComponentName.unflattenFromString("com.android/.TrustAgent"),
+                ComponentName.unflattenFromString("com.test/.TestAgent"));
+
+        mLockPatternUtils.setEnabledTrustAgents(enabledTrustAgents, testUserId);
+
+        assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent");
+    }
+
+    @Test
+    public void testGetEnabledTrustAgents() throws RemoteException {
+        int testUserId = 10;
+        ILockSettings ils = createTestLockSettings();
+        when(ils.getString(anyString(), any(), anyInt())).thenReturn(
+                "com.android/.TrustAgent,com.test/.TestAgent");
+
+        List<ComponentName> trustAgents = mLockPatternUtils.getEnabledTrustAgents(testUserId);
+
+        assertThat(trustAgents).containsExactly(
+                ComponentName.unflattenFromString("com.android/.TrustAgent"),
+                ComponentName.unflattenFromString("com.test/.TestAgent"));
+    }
+
+    @Test
+    public void testSetKnownTrustAgents() throws RemoteException {
+        int testUserId = 10;
+        ILockSettings ils = createTestLockSettings();
+        ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
+        doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt());
+        List<ComponentName> knownTrustAgents = Lists.newArrayList(
+                ComponentName.unflattenFromString("com.android/.TrustAgent"),
+                ComponentName.unflattenFromString("com.test/.TestAgent"));
+
+        mLockPatternUtils.setKnownTrustAgents(knownTrustAgents, testUserId);
+
+        assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent");
+    }
+
+    @Test
+    public void testGetKnownTrustAgents() throws RemoteException {
+        int testUserId = 10;
+        ILockSettings ils = createTestLockSettings();
+        when(ils.getString(anyString(), any(), anyInt())).thenReturn(
+                "com.android/.TrustAgent,com.test/.TestAgent");
+
+        List<ComponentName> trustAgents = mLockPatternUtils.getKnownTrustAgents(testUserId);
+
+        assertThat(trustAgents).containsExactly(
+                ComponentName.unflattenFromString("com.android/.TrustAgent"),
+                ComponentName.unflattenFromString("com.test/.TestAgent"));
+    }
+
+    @Test
+    public void isBiometricAllowedForUser_afterTrustagentExpired_returnsTrue()
+            throws RemoteException {
+        TestStrongAuthTracker tracker = createStrongAuthTracker();
+        tracker.changeStrongAuth(SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED);
+
+        assertTrue(tracker.isBiometricAllowedForUser(
+                /* isStrongBiometric = */ true,
+                DEMO_USER_ID));
+    }
+
+    @Test
+    public void isBiometricAllowedForUser_afterLockout_returnsFalse()
+            throws RemoteException {
+        TestStrongAuthTracker tracker = createStrongAuthTracker();
+        tracker.changeStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
+
+        assertFalse(tracker.isBiometricAllowedForUser(
+                /* isStrongBiometric = */ true,
+                DEMO_USER_ID));
+    }
 
     @Test
     public void testUserFrp_isNotRegularUser() throws Exception {
@@ -56,4 +315,49 @@
         assertNotEquals(UserHandle.USER_CURRENT, LockPatternUtils.USER_REPAIR_MODE);
         assertNotEquals(UserHandle.USER_CURRENT_OR_SELF, LockPatternUtils.USER_REPAIR_MODE);
     }
+
+    private TestStrongAuthTracker createStrongAuthTracker() {
+        final Context context = new ContextWrapper(InstrumentationRegistry.getTargetContext());
+        return new TestStrongAuthTracker(context, Looper.getMainLooper());
+    }
+
+    private static class TestStrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
+
+        TestStrongAuthTracker(Context context, Looper looper) {
+            super(context, looper);
+        }
+
+        public void changeStrongAuth(@StrongAuthFlags int strongAuthFlags) {
+            handleStrongAuthRequiredChanged(strongAuthFlags, DEMO_USER_ID);
+        }
+    }
+
+    private ILockSettings createTestLockSettings() {
+        final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+
+        final TrustManager trustManager = mock(TrustManager.class);
+        when(context.getSystemService(Context.TRUST_SERVICE)).thenReturn(trustManager);
+
+        final ILockSettings ils = mock(ILockSettings.class);
+        mLockPatternUtils = new LockPatternUtils(context, ils);
+        return ils;
+    }
+
+    private IWeakEscrowTokenActivatedListener createWeakEscrowTokenListener() {
+        return new IWeakEscrowTokenActivatedListener.Stub() {
+            @Override
+            public void onWeakEscrowTokenActivated(long handle, int userId) {
+                // Do nothing.
+            }
+        };
+    }
+
+    private IWeakEscrowTokenRemovedListener createTestAutoEscrowTokenRemovedListener() {
+        return new IWeakEscrowTokenRemovedListener.Stub() {
+            @Override
+            public void onWeakEscrowTokenRemoved(long handle, int userId) {
+                // Do nothing.
+            }
+        };
+    }
 }
diff --git a/core/tests/systemproperties/Android.bp b/core/tests/systemproperties/Android.bp
index 765ca3e..21aa3c44 100644
--- a/core/tests/systemproperties/Android.bp
+++ b/core/tests/systemproperties/Android.bp
@@ -15,6 +15,9 @@
     static_libs: [
         "android-common",
         "frameworks-core-util-lib",
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "ravenwood-junit",
     ],
     libs: [
         "android.test.runner",
@@ -23,3 +26,22 @@
     platform_apis: true,
     certificate: "platform",
 }
+
+android_ravenwood_test {
+    name: "FrameworksCoreSystemPropertiesTestsRavenwood",
+    static_libs: [
+        "android-common",
+        "frameworks-core-util-lib",
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "ravenwood-junit",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    srcs: [
+        "src/**/*.java",
+    ],
+    auto_gen_config: true,
+}
diff --git a/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java b/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
index 67783bf..ea65de0 100644
--- a/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
+++ b/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
@@ -16,19 +16,36 @@
 
 package android.os;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import junit.framework.TestCase;
+import org.junit.Rule;
+import org.junit.Test;
 
 import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
-public class SystemPropertiesTest extends TestCase {
+public class SystemPropertiesTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setSystemPropertyMutable(KEY, null)
+            .setSystemPropertyMutable(UNSET_KEY, null)
+            .setSystemPropertyMutable(PERSIST_KEY, null)
+            .build();
+
     private static final String KEY = "sys.testkey";
     private static final String UNSET_KEY = "Aiw7woh6ie4toh7W";
     private static final String PERSIST_KEY = "persist.sys.testkey";
 
+    @Test
     @SmallTest
     public void testStressPersistPropertyConsistency() throws Exception {
         for (int i = 0; i < 100; ++i) {
@@ -38,6 +55,7 @@
         }
     }
 
+    @Test
     @SmallTest
     public void testStressMemoryPropertyConsistency() throws Exception {
         for (int i = 0; i < 100; ++i) {
@@ -47,6 +65,7 @@
         }
     }
 
+    @Test
     @SmallTest
     public void testProperties() throws Exception {
         String value;
@@ -93,6 +112,7 @@
       assertEquals(expected, value);
     }
 
+    @Test
     @SmallTest
     public void testHandle() throws Exception {
         String value;
@@ -114,6 +134,7 @@
         assertEquals(12345, handle.getInt(12345));
     }
 
+    @Test
     @SmallTest
     public void testIntegralProperties() throws Exception {
         testInt("", 123, 123);
@@ -133,6 +154,7 @@
         testLong("-3147483647", 124, -3147483647L);
     }
 
+    @Test
     @SmallTest
     public void testUnset() throws Exception {
         assertEquals("abc", SystemProperties.get(UNSET_KEY, "abc"));
@@ -142,6 +164,7 @@
         assertEquals(-10, SystemProperties.getLong(UNSET_KEY, -10));
     }
 
+    @Test
     @SmallTest
     @SuppressWarnings("null")
     public void testNullKey() throws Exception {
@@ -176,6 +199,7 @@
         }
     }
 
+    @Test
     @SmallTest
     public void testCallbacks() {
         // Latches are not really necessary, but are easy to use.
@@ -220,6 +244,7 @@
         }
     }
 
+    @Test
     @SmallTest
     public void testDigestOf() {
         final String empty = SystemProperties.digestOf();
diff --git a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
deleted file mode 100644
index dcaf676..0000000
--- a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
+++ /dev/null
@@ -1,339 +0,0 @@
-/*
- * Copyright (C) 2017 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.internal.util;
-
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
-
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.pm.UserInfo;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.UserManager;
-import android.platform.test.annotations.IgnoreUnderRavenwood;
-import android.platform.test.ravenwood.RavenwoodRule;
-import android.provider.Settings;
-import android.test.mock.MockContentResolver;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.internal.widget.ILockSettings;
-import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
-import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
-import com.android.internal.widget.LockPatternUtils;
-
-import com.google.android.collect.Lists;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mockito;
-
-import java.nio.charset.StandardCharsets;
-import java.util.List;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@IgnoreUnderRavenwood(blockedBy = LockPatternUtils.class)
-public class LockPatternUtilsTest {
-    @Rule
-    public final RavenwoodRule mRavenwood = new RavenwoodRule();
-
-    private ILockSettings mLockSettings;
-    private static final int USER_ID = 1;
-    private static final int DEMO_USER_ID = 5;
-
-    private LockPatternUtils mLockPatternUtils;
-
-    private void configureTest(boolean isSecure, boolean isDemoUser, int deviceDemoMode)
-            throws Exception {
-        mLockSettings = Mockito.mock(ILockSettings.class);
-        final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
-
-        final MockContentResolver cr = new MockContentResolver(context);
-        cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
-        when(context.getContentResolver()).thenReturn(cr);
-        Settings.Global.putInt(cr, Settings.Global.DEVICE_DEMO_MODE, deviceDemoMode);
-
-        when(mLockSettings.getCredentialType(DEMO_USER_ID)).thenReturn(
-                isSecure ? LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
-                         : LockPatternUtils.CREDENTIAL_TYPE_NONE);
-        when(mLockSettings.getLong("lockscreen.password_type", PASSWORD_QUALITY_UNSPECIFIED,
-                DEMO_USER_ID)).thenReturn((long) PASSWORD_QUALITY_MANAGED);
-        // TODO(b/63758238): stop spying the class under test
-        mLockPatternUtils = spy(new LockPatternUtils(context));
-        when(mLockPatternUtils.getLockSettings()).thenReturn(mLockSettings);
-        doReturn(true).when(mLockPatternUtils).hasSecureLockScreen();
-
-        final UserInfo userInfo = Mockito.mock(UserInfo.class);
-        when(userInfo.isDemo()).thenReturn(isDemoUser);
-        final UserManager um = Mockito.mock(UserManager.class);
-        when(um.getUserInfo(DEMO_USER_ID)).thenReturn(userInfo);
-        when(context.getSystemService(Context.USER_SERVICE)).thenReturn(um);
-    }
-
-    @Test
-    public void isUserInLockDown() throws Exception {
-        configureTest(true, false, 2);
-
-        // GIVEN strong auth not required
-        when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(STRONG_AUTH_NOT_REQUIRED);
-
-        // THEN user isn't in lockdown
-        assertFalse(mLockPatternUtils.isUserInLockdown(USER_ID));
-
-        // GIVEN lockdown
-        when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(
-                STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
-
-        // THEN user is in lockdown
-        assertTrue(mLockPatternUtils.isUserInLockdown(USER_ID));
-
-        // GIVEN lockdown and lockout
-        when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(
-                STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN | STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
-
-        // THEN user is in lockdown
-        assertTrue(mLockPatternUtils.isUserInLockdown(USER_ID));
-    }
-
-    @Test
-    public void isLockScreenDisabled_isDemoUser_true() throws Exception {
-        configureTest(false, true, 2);
-        assertTrue(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
-    }
-
-    @Test
-    public void isLockScreenDisabled_isSecureAndDemoUser_false() throws Exception {
-        configureTest(true, true, 2);
-        assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
-    }
-
-    @Test
-    public void isLockScreenDisabled_isNotDemoUser_false() throws Exception {
-        configureTest(false, false, 2);
-        assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
-    }
-
-    @Test
-    public void isLockScreenDisabled_isNotInDemoMode_false() throws Exception {
-        configureTest(false, true, 0);
-        assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
-    }
-
-    @Test
-    public void testAddWeakEscrowToken() throws RemoteException {
-        ILockSettings ils = createTestLockSettings();
-        byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8);
-        int testUserId = 10;
-        IWeakEscrowTokenActivatedListener listener = createWeakEscrowTokenListener();
-        mLockPatternUtils.addWeakEscrowToken(testToken, testUserId, listener);
-        verify(ils).addWeakEscrowToken(eq(testToken), eq(testUserId), eq(listener));
-    }
-
-    @Test
-    public void testRegisterWeakEscrowTokenRemovedListener() throws RemoteException {
-        ILockSettings ils = createTestLockSettings();
-        IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener();
-        mLockPatternUtils.registerWeakEscrowTokenRemovedListener(testListener);
-        verify(ils).registerWeakEscrowTokenRemovedListener(eq(testListener));
-    }
-
-    @Test
-    public void testUnregisterWeakEscrowTokenRemovedListener() throws RemoteException {
-        ILockSettings ils = createTestLockSettings();
-        IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener();
-        mLockPatternUtils.unregisterWeakEscrowTokenRemovedListener(testListener);
-        verify(ils).unregisterWeakEscrowTokenRemovedListener(eq(testListener));
-    }
-
-    @Test
-    public void testRemoveAutoEscrowToken() throws RemoteException {
-        ILockSettings ils = createTestLockSettings();
-        int testUserId = 10;
-        long testHandle = 100L;
-        mLockPatternUtils.removeWeakEscrowToken(testHandle, testUserId);
-        verify(ils).removeWeakEscrowToken(eq(testHandle), eq(testUserId));
-    }
-
-    @Test
-    public void testIsAutoEscrowTokenActive() throws RemoteException {
-        ILockSettings ils = createTestLockSettings();
-        int testUserId = 10;
-        long testHandle = 100L;
-        mLockPatternUtils.isWeakEscrowTokenActive(testHandle, testUserId);
-        verify(ils).isWeakEscrowTokenActive(eq(testHandle), eq(testUserId));
-    }
-
-    @Test
-    public void testIsAutoEscrowTokenValid() throws RemoteException {
-        ILockSettings ils = createTestLockSettings();
-        int testUserId = 10;
-        byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8);
-        long testHandle = 100L;
-        mLockPatternUtils.isWeakEscrowTokenValid(testHandle, testToken, testUserId);
-        verify(ils).isWeakEscrowTokenValid(eq(testHandle), eq(testToken), eq(testUserId));
-    }
-
-    @Test
-    public void testSetEnabledTrustAgents() throws RemoteException {
-        int testUserId = 10;
-        ILockSettings ils = createTestLockSettings();
-        ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
-        doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt());
-        List<ComponentName> enabledTrustAgents = Lists.newArrayList(
-                ComponentName.unflattenFromString("com.android/.TrustAgent"),
-                ComponentName.unflattenFromString("com.test/.TestAgent"));
-
-        mLockPatternUtils.setEnabledTrustAgents(enabledTrustAgents, testUserId);
-
-        assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent");
-    }
-
-    @Test
-    public void testGetEnabledTrustAgents() throws RemoteException {
-        int testUserId = 10;
-        ILockSettings ils = createTestLockSettings();
-        when(ils.getString(anyString(), any(), anyInt())).thenReturn(
-                "com.android/.TrustAgent,com.test/.TestAgent");
-
-        List<ComponentName> trustAgents = mLockPatternUtils.getEnabledTrustAgents(testUserId);
-
-        assertThat(trustAgents).containsExactly(
-                ComponentName.unflattenFromString("com.android/.TrustAgent"),
-                ComponentName.unflattenFromString("com.test/.TestAgent"));
-    }
-
-    @Test
-    public void testSetKnownTrustAgents() throws RemoteException {
-        int testUserId = 10;
-        ILockSettings ils = createTestLockSettings();
-        ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
-        doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt());
-        List<ComponentName> knownTrustAgents = Lists.newArrayList(
-                ComponentName.unflattenFromString("com.android/.TrustAgent"),
-                ComponentName.unflattenFromString("com.test/.TestAgent"));
-
-        mLockPatternUtils.setKnownTrustAgents(knownTrustAgents, testUserId);
-
-        assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent");
-    }
-
-    @Test
-    public void testGetKnownTrustAgents() throws RemoteException {
-        int testUserId = 10;
-        ILockSettings ils = createTestLockSettings();
-        when(ils.getString(anyString(), any(), anyInt())).thenReturn(
-                "com.android/.TrustAgent,com.test/.TestAgent");
-
-        List<ComponentName> trustAgents = mLockPatternUtils.getKnownTrustAgents(testUserId);
-
-        assertThat(trustAgents).containsExactly(
-                ComponentName.unflattenFromString("com.android/.TrustAgent"),
-                ComponentName.unflattenFromString("com.test/.TestAgent"));
-    }
-
-    @Test
-    public void isBiometricAllowedForUser_afterTrustagentExpired_returnsTrue()
-            throws RemoteException {
-        TestStrongAuthTracker tracker = createStrongAuthTracker();
-        tracker.changeStrongAuth(SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED);
-
-        assertTrue(tracker.isBiometricAllowedForUser(
-                /* isStrongBiometric = */ true,
-                DEMO_USER_ID));
-    }
-
-    @Test
-    public void isBiometricAllowedForUser_afterLockout_returnsFalse()
-            throws RemoteException {
-        TestStrongAuthTracker tracker = createStrongAuthTracker();
-        tracker.changeStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
-
-        assertFalse(tracker.isBiometricAllowedForUser(
-                /* isStrongBiometric = */ true,
-                DEMO_USER_ID));
-    }
-
-
-    private TestStrongAuthTracker createStrongAuthTracker() {
-        final Context context = new ContextWrapper(InstrumentationRegistry.getTargetContext());
-        return new TestStrongAuthTracker(context, Looper.getMainLooper());
-    }
-
-    private static class TestStrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
-
-        TestStrongAuthTracker(Context context, Looper looper) {
-            super(context, looper);
-        }
-
-        public void changeStrongAuth(@StrongAuthFlags int strongAuthFlags) {
-            handleStrongAuthRequiredChanged(strongAuthFlags, DEMO_USER_ID);
-        }
-    }
-
-    private ILockSettings createTestLockSettings() {
-        final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
-        mLockPatternUtils = spy(new LockPatternUtils(context));
-        final ILockSettings ils = Mockito.mock(ILockSettings.class);
-        when(mLockPatternUtils.getLockSettings()).thenReturn(ils);
-        return ils;
-    }
-
-    private IWeakEscrowTokenActivatedListener createWeakEscrowTokenListener() {
-        return new IWeakEscrowTokenActivatedListener.Stub() {
-            @Override
-            public void onWeakEscrowTokenActivated(long handle, int userId) {
-                // Do nothing.
-            }
-        };
-    }
-
-    private IWeakEscrowTokenRemovedListener createTestAutoEscrowTokenRemovedListener() {
-        return new IWeakEscrowTokenRemovedListener.Stub() {
-            @Override
-            public void onWeakEscrowTokenRemoved(long handle, int userId) {
-                // Do nothing.
-            }
-        };
-    }
-}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 2de305f..62c9e16 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -450,7 +450,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.MANAGE_BIOMETRIC_DIALOG" />
+        <permission name="android.permission.SET_BIOMETRIC_DIALOG_LOGO" />
         <permission name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION" />
         <!-- 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 c77004d..da91a96 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1867,6 +1867,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/ActivityTaskSupervisor.java"
     },
+    "-483957611": {
+      "message": "Resuming configuration dispatch for %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-481924678": {
       "message": "handleNotObscuredLocked w: %s, w.mHasSurface: %b, w.isOnScreen(): %b, w.isDisplayedLw(): %b, w.mAttrs.userActivityTimeout: %d",
       "level": "DEBUG",
@@ -4021,6 +4027,12 @@
       "group": "WM_DEBUG_WINDOW_TRANSITIONS",
       "at": "com\/android\/server\/wm\/Transition.java"
     },
+    "1473051122": {
+      "message": "Pausing configuration dispatch for  %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "1494644409": {
       "message": "  Rejecting as detached: %s",
       "level": "VERBOSE",
diff --git a/libs/WindowManager/Shell/multivalentTests/OWNERS b/libs/WindowManager/Shell/multivalentTests/OWNERS
new file mode 100644
index 0000000..24c1a3a
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/OWNERS
@@ -0,0 +1,4 @@
+atsjenk@google.com
+liranb@google.com
+madym@google.com
+
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java
similarity index 92%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java
index 1b1ebc3..4cbb78f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 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,14 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.pip.phone;
+package com.android.wm.shell.common.pip;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.graphics.Rect;
 
-import com.android.wm.shell.common.pip.PipBoundsState;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -50,9 +48,9 @@
     @Retention(RetentionPolicy.SOURCE)
     @interface PipSizeSpec {}
 
-    static final int SIZE_SPEC_DEFAULT = 0;
-    static final int SIZE_SPEC_MAX = 1;
-    static final int SIZE_SPEC_CUSTOM = 2;
+    public static final int SIZE_SPEC_DEFAULT = 0;
+    public static final int SIZE_SPEC_MAX = 1;
+    public static final int SIZE_SPEC_CUSTOM = 2;
 
     /**
      * Returns MAX or DEFAULT {@link PipSizeSpec} to toggle to/from.
@@ -84,7 +82,7 @@
      * @return pip screen size to switch to
      */
     @PipSizeSpec
-    static int nextSizeSpec(@NonNull PipBoundsState mPipBoundsState,
+    public static int nextSizeSpec(@NonNull PipBoundsState mPipBoundsState,
             @NonNull Rect userResizeBounds) {
         // is pip screen at its maximum
         boolean isScreenMax = mPipBoundsState.getBounds().width()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 2dd2743..dbf7186 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -80,8 +80,7 @@
             Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartButtonClicked) {
         super(context, taskInfo, syncQueue, taskListener, displayLayout);
         mCallback = callback;
-        mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat
-                && shouldShowSizeCompatRestartButton(taskInfo);
+        mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
         mCameraCompatControlState = taskInfo.appCompatTaskInfo.cameraCompatControlState;
         mCompatUIHintsState = compatUIHintsState;
         mCompatUIConfiguration = compatUIConfiguration;
@@ -106,7 +105,8 @@
 
     @Override
     protected boolean eligibleToShowLayout() {
-        return mHasSizeCompat || shouldShowCameraControl();
+        return (mHasSizeCompat && shouldShowSizeCompatRestartButton(getLastTaskInfo()))
+                || shouldShowCameraControl();
     }
 
     @Override
@@ -114,11 +114,6 @@
         mLayout = inflateLayout();
         mLayout.inject(this);
 
-        final TaskInfo taskInfo = getLastTaskInfo();
-        if (taskInfo != null) {
-            mHasSizeCompat = mHasSizeCompat && shouldShowSizeCompatRestartButton(taskInfo);
-        }
-
         updateVisibilityOfViews();
 
         if (mHasSizeCompat) {
@@ -139,8 +134,7 @@
             boolean canShow) {
         final boolean prevHasSizeCompat = mHasSizeCompat;
         final int prevCameraCompatControlState = mCameraCompatControlState;
-        mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat
-                && shouldShowSizeCompatRestartButton(taskInfo);
+        mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
         mCameraCompatControlState = taskInfo.appCompatTaskInfo.cameraCompatControlState;
 
         if (!super.updateCompatInfo(taskInfo, taskListener, canShow)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
index 180498c..0564c95 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
@@ -332,7 +332,7 @@
         updateSurfacePosition();
     }
 
-    @Nullable
+    @NonNull
     protected TaskInfo getLastTaskInfo() {
         return mTaskInfo;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 3b48c67..7b98fa6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -50,15 +50,16 @@
 public abstract class Pip2Module {
     @WMSingleton
     @Provides
-    static PipTransition providePipTransition(@NonNull ShellInit shellInit,
+    static PipTransition providePipTransition(Context context,
+            @NonNull ShellInit shellInit,
             @NonNull ShellTaskOrganizer shellTaskOrganizer,
             @NonNull Transitions transitions,
             PipBoundsState pipBoundsState,
             PipBoundsAlgorithm pipBoundsAlgorithm,
             Optional<PipController> pipController,
             @NonNull PipScheduler pipScheduler) {
-        return new PipTransition(shellInit, shellTaskOrganizer, transitions, pipBoundsState, null,
-                pipBoundsAlgorithm, pipScheduler);
+        return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
+                pipBoundsState, null, pipBoundsAlgorithm, pipScheduler);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 04911c0..0e70736 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -47,6 +47,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Consumer;
 
 /**
  * Responsible supplying PiP Transitions.
@@ -116,6 +117,17 @@
     }
 
     /**
+     * Called when the Shell wants to start resizing Pip transition/animation.
+     *
+     * @param onFinishResizeCallback callback guaranteed to execute when animation ends and
+     *                               client completes any potential draws upon WM state updates.
+     */
+    public void startResizeTransition(WindowContainerTransaction wct,
+            Consumer<Rect> onFinishResizeCallback) {
+        // Default implementation does nothing.
+    }
+
+    /**
      * Called when the transition animation can't continue (eg. task is removed during
      * animation)
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 452a416..81705e2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -52,6 +52,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDoubleTapHelper;
 import com.android.wm.shell.common.pip.PipUiEventLogger;
 import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.common.pip.SizeSpecSource;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index 0b8f60e..57b73b3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -24,10 +24,12 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.graphics.Rect;
 import android.view.SurfaceControl;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
+import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
 import androidx.core.content.ContextCompat;
 
@@ -36,6 +38,10 @@
 import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.pip.PipTransitionController;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.function.Consumer;
+
 /**
  * Scheduler for Shell initiated PiP transitions and animations.
  */
@@ -58,13 +64,37 @@
     private SurfaceControl mPinnedTaskLeash;
 
     /**
-     * A temporary broadcast receiver to initiate exit PiP via expand.
-     * This will later be modified to be triggered by the PiP menu.
+     * Temporary PiP CUJ codes to schedule PiP related transitions directly from Shell.
+     * This is used for a broadcast receiver to resolve intents. This should be removed once
+     * there is an equivalent of PipTouchHandler and PipResizeGestureHandler for PiP2.
+     */
+    private static final int PIP_EXIT_VIA_EXPAND_CODE = 0;
+    private static final int PIP_DOUBLE_TAP = 1;
+
+    @IntDef(value = {
+            PIP_EXIT_VIA_EXPAND_CODE,
+            PIP_DOUBLE_TAP
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface PipUserJourneyCode {}
+
+    /**
+     * A temporary broadcast receiver to initiate PiP CUJs.
      */
     private class PipSchedulerReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
-            scheduleExitPipViaExpand();
+            int userJourneyCode = intent.getIntExtra("cuj_code_extra", 0);
+            switch (userJourneyCode) {
+                case PIP_EXIT_VIA_EXPAND_CODE:
+                    scheduleExitPipViaExpand();
+                    break;
+                case PIP_DOUBLE_TAP:
+                    scheduleDoubleTapToResize();
+                    break;
+                default:
+                    throw new IllegalStateException("unexpected CUJ code=" + userJourneyCode);
+            }
         }
     }
 
@@ -121,6 +151,23 @@
         }
     }
 
+    /**
+     * Schedules resize PiP via double tap.
+     */
+    public void scheduleDoubleTapToResize() {}
+
+    /**
+     * Animates resizing of the pinned stack given the duration.
+     */
+    public void scheduleAnimateResizePip(Rect toBounds, Consumer<Rect> onFinishResizeCallback) {
+        if (mPipTaskToken == null) {
+            return;
+        }
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.setBounds(mPipTaskToken, toBounds);
+        mPipTransitionController.startResizeTransition(wct, onFinishResizeCallback);
+    }
+
     void onExitPip() {
         mPipTaskToken = null;
         mPinnedTaskLeash = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 3b0e7c1..f3d178a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -22,10 +22,12 @@
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 
 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_RESIZE_PIP;
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.PictureInPictureParams;
+import android.content.Context;
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.view.SurfaceControl;
@@ -36,6 +38,7 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.common.pip.PipBoundsState;
@@ -45,25 +48,29 @@
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 
+import java.util.function.Consumer;
+
 /**
  * Implementation of transitions for PiP on phone.
  */
 public class PipTransition extends PipTransitionController {
     private static final String TAG = PipTransition.class.getSimpleName();
 
+    private final Context mContext;
     private final PipScheduler mPipScheduler;
     @Nullable
     private WindowContainerToken mPipTaskToken;
     @Nullable
     private IBinder mEnterTransition;
     @Nullable
-    private IBinder mAutoEnterButtonNavTransition;
-    @Nullable
     private IBinder mExitViaExpandTransition;
     @Nullable
-    private IBinder mLegacyEnterTransition;
+    private IBinder mResizeTransition;
+
+    private Consumer<Rect> mFinishResizeCallback;
 
     public PipTransition(
+            Context context,
             @NonNull ShellInit shellInit,
             @NonNull ShellTaskOrganizer shellTaskOrganizer,
             @NonNull Transitions transitions,
@@ -74,6 +81,7 @@
         super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
                 pipBoundsAlgorithm);
 
+        mContext = context;
         mPipScheduler = pipScheduler;
         mPipScheduler.setPipTransitionController(this);
     }
@@ -87,7 +95,7 @@
 
     @Override
     public void startExitTransition(int type, WindowContainerTransaction out,
-            @android.annotation.Nullable Rect destinationBounds) {
+            @Nullable Rect destinationBounds) {
         if (out == null) {
             return;
         }
@@ -97,6 +105,16 @@
         }
     }
 
+    @Override
+    public void startResizeTransition(WindowContainerTransaction wct,
+            Consumer<Rect> onFinishResizeCallback) {
+        if (wct == null) {
+            return;
+        }
+        mResizeTransition = mTransitions.startTransition(TRANSIT_RESIZE_PIP, wct, this);
+        mFinishResizeCallback = onFinishResizeCallback;
+    }
+
     @Nullable
     @Override
     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@@ -126,43 +144,6 @@
     public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
             @Nullable SurfaceControl.Transaction finishT) {}
 
-    private WindowContainerTransaction getEnterPipTransaction(@NonNull IBinder transition,
-            @NonNull TransitionRequestInfo request) {
-        // cache the original task token to check for multi-activity case later
-        final ActivityManager.RunningTaskInfo pipTask = request.getPipTask();
-        PictureInPictureParams pipParams = pipTask.pictureInPictureParams;
-        mPipBoundsState.setBoundsStateForEntry(pipTask.topActivity, pipTask.topActivityInfo,
-                pipParams, mPipBoundsAlgorithm);
-
-        // calculate the entry bounds and notify core to move task to pinned with final bounds
-        final Rect entryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
-        mPipBoundsState.setBounds(entryBounds);
-
-        WindowContainerTransaction wct = new WindowContainerTransaction();
-        wct.movePipActivityToPinnedRootTask(pipTask.token, entryBounds);
-        return wct;
-    }
-
-    private boolean isAutoEnterInButtonNavigation(@NonNull TransitionRequestInfo requestInfo) {
-        final ActivityManager.RunningTaskInfo pipTask = requestInfo.getPipTask();
-        if (pipTask == null) {
-            return false;
-        }
-        if (pipTask.pictureInPictureParams == null) {
-            return false;
-        }
-
-        // Assuming auto-enter is enabled and pipTask is non-null, the TRANSIT_OPEN request type
-        // implies that we are entering PiP in button navigation mode. This is guaranteed by
-        // TaskFragment#startPausing()` in Core which wouldn't get called in gesture nav.
-        return requestInfo.getType() == TRANSIT_OPEN
-                && pipTask.pictureInPictureParams.isAutoEnterEnabled();
-    }
-
-    private boolean isEnterPictureInPictureModeRequest(@NonNull TransitionRequestInfo requestInfo) {
-        return requestInfo.getType() == TRANSIT_PIP;
-    }
-
     @Override
     public boolean startAnimation(@NonNull IBinder transition,
             @NonNull TransitionInfo info,
@@ -182,16 +163,48 @@
         } else if (transition == mExitViaExpandTransition) {
             mExitViaExpandTransition = null;
             return startExpandAnimation(info, startTransaction, finishTransaction, finishCallback);
+        } else if (transition == mResizeTransition) {
+            mResizeTransition = null;
+            return startResizeAnimation(info, startTransaction, finishTransaction, finishCallback);
         }
         return false;
     }
 
-    private boolean isLegacyEnter(@NonNull TransitionInfo info) {
+    private boolean startResizeAnimation(@NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
         TransitionInfo.Change pipChange = getPipChange(info);
-        // If the only change in the changes list is a TO_FRONT mode PiP task,
-        // then this is legacy-enter PiP.
-        return pipChange != null && pipChange.getMode() == TRANSIT_TO_FRONT
-                && info.getChanges().size() == 1;
+        if (pipChange == null) {
+            return false;
+        }
+        SurfaceControl pipLeash = pipChange.getLeash();
+        Rect destinationBounds = pipChange.getEndAbsBounds();
+
+        // Even though the final bounds and crop are applied with finishTransaction since
+        // this is a visible change, we still need to handle the app draw coming in. Snapshot
+        // covering app draw during collection will be removed by startTransaction. So we make
+        // the crop equal to the final bounds and then scale the leash back to starting bounds.
+        startTransaction.setWindowCrop(pipLeash, pipChange.getEndAbsBounds().width(),
+                pipChange.getEndAbsBounds().height());
+        startTransaction.setScale(pipLeash,
+                (float) mPipBoundsState.getBounds().width() / destinationBounds.width(),
+                (float) mPipBoundsState.getBounds().height() / destinationBounds.height());
+        startTransaction.apply();
+
+        finishTransaction.setScale(pipLeash,
+                (float) mPipBoundsState.getBounds().width() / destinationBounds.width(),
+                (float) mPipBoundsState.getBounds().height() / destinationBounds.height());
+
+        // We are done with the transition, but will continue animating leash to final bounds.
+        finishCallback.onTransitionFinished(null);
+
+        // Animate the pip leash with the new buffer
+        final int duration = mContext.getResources().getInteger(
+                R.integer.config_pipResizeAnimationDuration);
+        // TODO: b/275910498 Couple this routine with a new implementation of the PiP animator.
+        startResizeAnimation(pipLeash, mPipBoundsState.getBounds(), destinationBounds, duration);
+        return true;
     }
 
     private boolean startBoundsTypeEnterAnimation(@NonNull TransitionInfo info,
@@ -251,6 +264,57 @@
         return null;
     }
 
+    private WindowContainerTransaction getEnterPipTransaction(@NonNull IBinder transition,
+            @NonNull TransitionRequestInfo request) {
+        // cache the original task token to check for multi-activity case later
+        final ActivityManager.RunningTaskInfo pipTask = request.getPipTask();
+        PictureInPictureParams pipParams = pipTask.pictureInPictureParams;
+        mPipBoundsState.setBoundsStateForEntry(pipTask.topActivity, pipTask.topActivityInfo,
+                pipParams, mPipBoundsAlgorithm);
+
+        // calculate the entry bounds and notify core to move task to pinned with final bounds
+        final Rect entryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
+        mPipBoundsState.setBounds(entryBounds);
+
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.movePipActivityToPinnedRootTask(pipTask.token, entryBounds);
+        return wct;
+    }
+
+    private boolean isAutoEnterInButtonNavigation(@NonNull TransitionRequestInfo requestInfo) {
+        final ActivityManager.RunningTaskInfo pipTask = requestInfo.getPipTask();
+        if (pipTask == null) {
+            return false;
+        }
+        if (pipTask.pictureInPictureParams == null) {
+            return false;
+        }
+
+        // Assuming auto-enter is enabled and pipTask is non-null, the TRANSIT_OPEN request type
+        // implies that we are entering PiP in button navigation mode. This is guaranteed by
+        // TaskFragment#startPausing()` in Core which wouldn't get called in gesture nav.
+        return requestInfo.getType() == TRANSIT_OPEN
+                && pipTask.pictureInPictureParams.isAutoEnterEnabled();
+    }
+
+    private boolean isEnterPictureInPictureModeRequest(@NonNull TransitionRequestInfo requestInfo) {
+        return requestInfo.getType() == TRANSIT_PIP;
+    }
+
+    private boolean isLegacyEnter(@NonNull TransitionInfo info) {
+        TransitionInfo.Change pipChange = getPipChange(info);
+        // If the only change in the changes list is a TO_FRONT mode PiP task,
+        // then this is legacy-enter PiP.
+        return pipChange != null && pipChange.getMode() == TRANSIT_TO_FRONT
+                && info.getChanges().size() == 1;
+    }
+
+    /**
+     * TODO: b/275910498 Use a new implementation of the PiP animator here.
+     */
+    private void startResizeAnimation(SurfaceControl leash, Rect startBounds,
+            Rect endBounds, int duration) {}
+
     private void onExitPip() {
         mPipTaskToken = null;
         mPipScheduler.onExitPip();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index d023cea..1232baa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -1020,7 +1020,7 @@
                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
                                 "RecentsController.finishInner: no valid PiP leash;"
                                         + "mPipTransaction=%s, mPipTask=%s, mPipTaskId=%d",
-                                mPipTransaction.toString(), mPipTask.toString(), mPipTaskId);
+                                mPipTransaction, mPipTask, mPipTaskId);
                     } else {
                         t.show(pipLeash);
                         PictureInPictureSurfaceTransaction.apply(mPipTransaction, pipLeash, t);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index 253acc4..0ca244c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -158,5 +158,10 @@
      * does not expect split to currently be running.
      */
     RemoteAnimationTarget[] onStartingSplitLegacy(in RemoteAnimationTarget[] appTargets) = 14;
+
+    /**
+     * Reverse the split.
+     */
+    oneway void switchSplitPosition() = 22;
 }
-// Last id = 21
\ No newline at end of file
+// Last id = 22
\ No newline at end of file
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 2ec52bb..70cb2fc 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
@@ -1109,6 +1109,12 @@
         mStageCoordinator.onDroppedToSplit(position, dragSessionId);
     }
 
+    void switchSplitPosition(String reason) {
+        if (isSplitScreenVisible()) {
+            mStageCoordinator.switchSplitPosition(reason);
+        }
+    }
+
     /**
      * Return the {@param exitReason} as a string.
      */
@@ -1473,5 +1479,11 @@
                     true /* blocking */);
             return out[0];
         }
+
+        @Override
+        public void switchSplitPosition() {
+            executeRemoteCallWithTaskPermission(mController, "switchSplitPosition",
+                    (controller) -> controller.switchSplitPosition("remoteCall"));
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
index 7fd03a9..7f16c5e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
@@ -43,6 +43,8 @@
                 return runRemoveFromSideStage(args, pw);
             case "setSideStagePosition":
                 return runSetSideStagePosition(args, pw);
+            case "switchSplitPosition":
+                return runSwitchSplitPosition();
             default:
                 pw.println("Invalid command: " + args[0]);
                 return false;
@@ -84,6 +86,11 @@
         return true;
     }
 
+    private boolean runSwitchSplitPosition() {
+        mController.switchSplitPosition("shellCommand");
+        return true;
+    }
+
     @Override
     public void printShellCommandHelp(PrintWriter pw, String prefix) {
         pw.println(prefix + "moveToSideStage <taskId> <SideStagePosition>");
@@ -92,5 +99,7 @@
         pw.println(prefix + "  Remove a task with given id in split-screen mode.");
         pw.println(prefix + "setSideStagePosition <SideStagePosition>");
         pw.println(prefix + "  Sets the position of the side-stage.");
+        pw.println(prefix + "switchSplitPosition");
+        pw.println(prefix + "  Reverses the split.");
     }
 }
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 3fb0dbf..67fc7e2 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
@@ -175,6 +175,9 @@
     /** Transition to animate task to desktop. */
     public static final int TRANSIT_MOVE_TO_DESKTOP = WindowManager.TRANSIT_FIRST_CUSTOM + 15;
 
+    /** Transition to resize PiP task. */
+    public static final int TRANSIT_RESIZE_PIP = TRANSIT_FIRST_CUSTOM + 16;
+
     private final ShellTaskOrganizer mOrganizer;
     private final Context mContext;
     private final ShellExecutor mMainExecutor;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 5a74255..e6faa63 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -93,7 +93,7 @@
 
         // On a smaller screen, don't require as much empty space on screen, as offscreen
         // drags will be restricted too much.
-        final int requiredEmptySpaceId = mDisplayController.getDisplayContext(mTaskInfo.taskId)
+        final int requiredEmptySpaceId = mDisplayController.getDisplayContext(mTaskInfo.displayId)
                 .getResources().getConfiguration().smallestScreenWidthDp >= 600
                 ? R.dimen.freeform_required_visible_empty_space_in_header :
                 R.dimen.small_screen_required_visible_empty_space_in_header;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
index 4ddc539..dd358e7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
@@ -30,6 +30,7 @@
 import android.app.ActivityManager;
 import android.app.AppCompatTaskInfo.CameraCompatControlState;
 import android.app.TaskInfo;
+import android.graphics.Rect;
 import android.testing.AndroidTestingRunner;
 import android.util.Pair;
 import android.view.LayoutInflater;
@@ -83,6 +84,7 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        doReturn(100).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
         mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN);
         mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
                 mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
@@ -127,7 +129,6 @@
     @Test
     public void testOnClickForSizeCompatHint() {
         mWindowManager.mHasSizeCompat = true;
-        doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(mTaskInfo);
         mWindowManager.createLayout(/* canShow= */ true);
         final LinearLayout sizeCompatHint = mLayout.findViewById(R.id.size_compat_hint);
         sizeCompatHint.performClick();
@@ -222,6 +223,9 @@
         taskInfo.taskId = TASK_ID;
         taskInfo.appCompatTaskInfo.topActivityInSizeCompat = hasSizeCompat;
         taskInfo.appCompatTaskInfo.cameraCompatControlState = cameraCompatControlState;
+        taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 1000;
+        taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000;
+        taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 2000, 2000));
         return taskInfo;
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index 2acfd83..4f261cd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -20,7 +20,6 @@
 import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
 import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
 import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
 import static android.view.WindowInsets.Type.navigationBars;
 
@@ -86,6 +85,8 @@
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
 
     private static final int TASK_ID = 1;
+    private static final int TASK_WIDTH = 2000;
+    private static final int TASK_HEIGHT = 2000;
 
     @Mock private SyncTransactionQueue mSyncTransactionQueue;
     @Mock private CompatUIController.CompatUICallback mCallback;
@@ -101,6 +102,7 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        doReturn(100).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
         mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN);
         mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
                 mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
@@ -115,7 +117,6 @@
     public void testCreateSizeCompatButton() {
         // Doesn't create layout if show is false.
         mWindowManager.mHasSizeCompat = true;
-        doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(mTaskInfo);
         assertTrue(mWindowManager.createLayout(/* canShow= */ false));
 
         verify(mWindowManager, never()).inflateLayout();
@@ -147,6 +148,13 @@
         mWindowManager.mHasSizeCompat = false;
         assertFalse(mWindowManager.createLayout(/* canShow= */ true));
 
+        // Returns false and doesn't create layout if restart button should be hidden.
+        clearInvocations(mWindowManager);
+        mWindowManager.mHasSizeCompat = true;
+        mTaskInfo.appCompatTaskInfo.topActivityLetterboxWidth = TASK_WIDTH;
+        mTaskInfo.appCompatTaskInfo.topActivityLetterboxHeight = TASK_HEIGHT;
+        assertFalse(mWindowManager.createLayout(/* canShow= */ true));
+
         verify(mWindowManager, never()).inflateLayout();
     }
 
@@ -293,8 +301,6 @@
 
     @Test
     public void testUpdateCompatInfoLayoutNotInflatedYet() {
-        mWindowManager.mHasSizeCompat = true;
-        doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(any());
         mWindowManager.createLayout(/* canShow= */ false);
 
         verify(mWindowManager, never()).inflateLayout();
@@ -314,6 +320,15 @@
         mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
 
         verify(mWindowManager).inflateLayout();
+
+        // Change shouldShowSizeCompatRestartButton to false and pass canShow true, layout
+        // shouldn't be inflated
+        clearInvocations(mWindowManager);
+        taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = TASK_WIDTH;
+        taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = TASK_HEIGHT;
+        mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
+
+        verify(mWindowManager, never()).inflateLayout();
     }
 
     @Test
@@ -364,7 +379,6 @@
         // Create button if it is not created.
         mWindowManager.mLayout = null;
         mWindowManager.mHasSizeCompat = true;
-        doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(mTaskInfo);
         mWindowManager.updateVisibility(/* canShow= */ true);
 
         verify(mWindowManager).createLayout(/* canShow= */ true);
@@ -489,7 +503,6 @@
         TaskInfo taskInfo = createTaskInfo(true, CAMERA_COMPAT_CONTROL_HIDDEN);
         taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 2000, 2000));
         taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 2000;
-        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1850;
 
         assertFalse(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
@@ -514,6 +527,11 @@
         taskInfo.appCompatTaskInfo.topActivityInSizeCompat = hasSizeCompat;
         taskInfo.appCompatTaskInfo.cameraCompatControlState = cameraCompatControlState;
         taskInfo.configuration.uiMode &= ~Configuration.UI_MODE_TYPE_DESK;
+        // Letterboxed activity that takes half the screen should show size compat restart button
+        taskInfo.configuration.windowConfiguration.setBounds(
+                new Rect(0, 0, TASK_WIDTH, TASK_HEIGHT));
+        taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 1000;
+        taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000;
         return taskInfo;
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java
index 0f8db85..b583acd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java
@@ -16,10 +16,10 @@
 
 package com.android.wm.shell.pip.phone;
 
-import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.SIZE_SPEC_CUSTOM;
-import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.SIZE_SPEC_DEFAULT;
-import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.SIZE_SPEC_MAX;
-import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.nextSizeSpec;
+import static com.android.wm.shell.common.pip.PipDoubleTapHelper.SIZE_SPEC_CUSTOM;
+import static com.android.wm.shell.common.pip.PipDoubleTapHelper.SIZE_SPEC_DEFAULT;
+import static com.android.wm.shell.common.pip.PipDoubleTapHelper.SIZE_SPEC_MAX;
+import static com.android.wm.shell.common.pip.PipDoubleTapHelper.nextSizeSpec;
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -30,6 +30,7 @@
 
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDoubleTapHelper;
 
 import org.junit.Assert;
 import org.junit.Before;
@@ -38,7 +39,7 @@
 import org.mockito.Mock;
 
 /**
- * Unit test against {@link PipDoubleTapHelper}.
+ * Unit test against {@link com.android.wm.shell.common.pip.PipDoubleTapHelper}.
  */
 @RunWith(AndroidTestingRunner.class)
 public class PipDoubleTapHelperTest extends ShellTestCase {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 12a5594..7f3bfbb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -421,6 +421,15 @@
         assertEquals(false, controller.supportsMultiInstanceSplit(component));
     }
 
+    @Test
+    public void testSwitchSplitPosition_checksIsSplitScreenVisible() {
+        final String reason = "test";
+        when(mSplitScreenController.isSplitScreenVisible()).thenReturn(true, false);
+        mSplitScreenController.switchSplitPosition(reason);
+        mSplitScreenController.switchSplitPosition(reason);
+        verify(mStageCoordinator, times(1)).switchSplitPosition(reason);
+    }
+
     private Intent createStartIntent(String activityName) {
         Intent intent = new Intent();
         intent.setComponent(new ComponentName(mContext, activityName));
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index b40b73c..0abb6f5 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -229,15 +229,6 @@
     path: "apex/java",
 }
 
-java_api_contribution {
-    name: "framework-graphics-public-stubs",
-    api_surface: "public",
-    api_file: "api/current.txt",
-    visibility: [
-        "//build/orchestrator/apis",
-    ],
-}
-
 // ------------------------
 // APEX
 // ------------------------
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 691aa77..425db06 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -183,23 +183,23 @@
      *       preference} passed by a proxy router. Use {@link RouteDiscoveryPreference#EMPTY} when
      *       setting a route callback.
      *   <li>
-     *       <p>Methods returning non-system {@link RoutingController controllers} always return
-     *       new instances with the latest data. Do not attempt to compare or store them. Instead,
-     *       use {@link #getController(String)} or {@link #getControllers()} to query the most
+     *       <p>Methods returning non-system {@link RoutingController controllers} always return new
+     *       instances with the latest data. Do not attempt to compare or store them. Instead, use
+     *       {@link #getController(String)} or {@link #getControllers()} to query the most
      *       up-to-date state.
      *   <li>
      *       <p>Calls to {@link #setOnGetControllerHintsListener} are ignored.
      * </ul>
      *
      * @param clientPackageName the package name of the app to control
-     * @throws SecurityException if the caller doesn't have {@link
-     *     Manifest.permission#MEDIA_CONTENT_CONTROL MEDIA_CONTENT_CONTROL} permission.
-     * @hide
+     * @return a proxy MediaRouter2 instance if {@code clientPackageName} exists or {@code null}.
      */
-    // TODO (b/311711420): Deprecate once #getInstance(Context, String, UserHandle) reaches public
-    //  SDK.
-    @SystemApi
-    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
+    @FlaggedApi(FLAG_ENABLE_CROSS_USER_ROUTING_IN_MEDIA_ROUTER2)
+    @RequiresPermission(
+            anyOf = {
+                Manifest.permission.MEDIA_CONTENT_CONTROL,
+                Manifest.permission.MEDIA_ROUTING_CONTROL
+            })
     @Nullable
     public static MediaRouter2 getInstance(
             @NonNull Context context, @NonNull String clientPackageName) {
@@ -226,9 +226,9 @@
      *       {@link RouteDiscoveryPreference.Builder#setPreferredFeatures(List) preferred features}
      *       when setting a route callback.
      *   <li>
-     *       <p>Methods returning non-system {@link RoutingController controllers} always return
-     *       new instances with the latest data. Do not attempt to compare or store them. Instead,
-     *       use {@link #getController(String)} or {@link #getControllers()} to query the most
+     *       <p>Methods returning non-system {@link RoutingController controllers} always return new
+     *       instances with the latest data. Do not attempt to compare or store them. Instead, use
+     *       {@link #getController(String)} or {@link #getControllers()} to query the most
      *       up-to-date state.
      *   <li>
      *       <p>Calls to {@link #setOnGetControllerHintsListener} are ignored.
@@ -242,8 +242,8 @@
      * @throws SecurityException if {@code user} does not match {@link Process#myUserHandle()} and
      *     the caller does not hold {@code Manifest.permission#INTERACT_ACROSS_USERS_FULL}.
      * @throws IllegalArgumentException if {@code clientPackageName} does not exist in {@code user}.
+     * @hide
      */
-    @FlaggedApi(FLAG_ENABLE_CROSS_USER_ROUTING_IN_MEDIA_ROUTER2)
     @RequiresPermission(
             anyOf = {
                 Manifest.permission.MEDIA_CONTENT_CONTROL,
@@ -251,9 +251,7 @@
             })
     @NonNull
     public static MediaRouter2 getInstance(
-            @NonNull Context context,
-            @NonNull String clientPackageName,
-            @NonNull UserHandle user) {
+            @NonNull Context context, @NonNull String clientPackageName, @NonNull UserHandle user) {
         return findOrCreateProxyInstanceForCallingUser(context, clientPackageName, user);
     }
 
diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java
index d28c26d..2202766 100644
--- a/media/java/android/media/RoutingSessionInfo.java
+++ b/media/java/android/media/RoutingSessionInfo.java
@@ -182,7 +182,7 @@
         mControlHints = src.readBundle();
         mIsSystemSession = src.readBoolean();
         mTransferReason = src.readInt();
-        mTransferInitiatorUserHandle = src.readParcelable(null, android.os.UserHandle.class);
+        mTransferInitiatorUserHandle = UserHandle.readFromParcel(src);
         mTransferInitiatorPackageName = src.readString();
     }
 
@@ -417,11 +417,7 @@
         dest.writeBundle(mControlHints);
         dest.writeBoolean(mIsSystemSession);
         dest.writeInt(mTransferReason);
-        if (mTransferInitiatorUserHandle != null) {
-            mTransferInitiatorUserHandle.writeToParcel(dest, /* flags= */ 0);
-        } else {
-            dest.writeParcelable(null, /* flags= */ 0);
-        }
+        UserHandle.writeToParcel(mTransferInitiatorUserHandle, dest);
         dest.writeString(mTransferInitiatorPackageName);
     }
 
diff --git a/media/java/android/media/flags/editing.aconfig b/media/java/android/media/flags/editing.aconfig
new file mode 100644
index 0000000..c3997e9
--- /dev/null
+++ b/media/java/android/media/flags/editing.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.media.editing.flags"
+
+flag {
+  name: "add_media_metrics_editing"
+  namespace: "media_solutions"
+  description: "Add media metrics for transcoding/editing events."
+  bug: "297487694"
+}
diff --git a/media/java/android/media/metrics/EditingEndedEvent.aidl b/media/java/android/media/metrics/EditingEndedEvent.aidl
new file mode 100644
index 0000000..e099dea
--- /dev/null
+++ b/media/java/android/media/metrics/EditingEndedEvent.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.media.metrics;
+
+parcelable EditingEndedEvent;
diff --git a/media/java/android/media/metrics/EditingEndedEvent.java b/media/java/android/media/metrics/EditingEndedEvent.java
new file mode 100644
index 0000000..72e6db8
--- /dev/null
+++ b/media/java/android/media/metrics/EditingEndedEvent.java
@@ -0,0 +1,325 @@
+/*
+ * 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.media.metrics;
+
+import static com.android.media.editing.flags.Flags.FLAG_ADD_MEDIA_METRICS_EDITING;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.util.Objects;
+
+/** Event for an editing operation having ended. */
+@FlaggedApi(FLAG_ADD_MEDIA_METRICS_EDITING)
+public final class EditingEndedEvent extends Event implements Parcelable {
+
+    // The special value 0 is reserved for the field being unspecified in the proto.
+
+    /** The editing operation was successful. */
+    public static final int FINAL_STATE_SUCCEEDED = 1;
+
+    /** The editing operation was canceled. */
+    public static final int FINAL_STATE_CANCELED = 2;
+
+    /** The editing operation failed due to an error. */
+    public static final int FINAL_STATE_ERROR = 3;
+
+    /** @hide */
+    @IntDef(
+            prefix = {"FINAL_STATE_"},
+            value = {
+                FINAL_STATE_SUCCEEDED,
+                FINAL_STATE_CANCELED,
+                FINAL_STATE_ERROR,
+            })
+    @Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+    public @interface FinalState {}
+
+    private final @FinalState int mFinalState;
+
+    // The special value 0 is reserved for the field being unspecified in the proto.
+
+    /** Special value representing that no error occurred. */
+    public static final int ERROR_CODE_NONE = 1;
+
+    /** Error code for unexpected runtime errors. */
+    public static final int ERROR_CODE_FAILED_RUNTIME_CHECK = 2;
+
+    /** Error code for non-specific errors during input/output. */
+    public static final int ERROR_CODE_IO_UNSPECIFIED = 3;
+
+    /** Error code for network connection failures. */
+    public static final int ERROR_CODE_IO_NETWORK_CONNECTION_FAILED = 4;
+
+    /** Error code for network timeouts. */
+    public static final int ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT = 5;
+
+    /** Caused by an HTTP server returning an unexpected HTTP response status code. */
+    public static final int ERROR_CODE_IO_BAD_HTTP_STATUS = 6;
+
+    /** Caused by a non-existent file. */
+    public static final int ERROR_CODE_IO_FILE_NOT_FOUND = 7;
+
+    /**
+     * Caused by lack of permission to perform an IO operation. For example, lack of permission to
+     * access internet or external storage.
+     */
+    public static final int ERROR_CODE_IO_NO_PERMISSION = 8;
+
+    /** */
+    public static final int ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED = 9;
+
+    /** Caused by reading data out of the data bounds. */
+    public static final int ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE = 10;
+
+    /** Caused by a decoder initialization failure. */
+    public static final int ERROR_CODE_DECODER_INIT_FAILED = 11;
+
+    /** Caused by a failure while trying to decode media samples. */
+    public static final int ERROR_CODE_DECODING_FAILED = 12;
+
+    /** Caused by trying to decode content whose format is not supported. */
+    public static final int ERROR_CODE_DECODING_FORMAT_UNSUPPORTED = 13;
+
+    /** Caused by an encoder initialization failure. */
+    public static final int ERROR_CODE_ENCODER_INIT_FAILED = 14;
+
+    /** Caused by a failure while trying to encode media samples. */
+    public static final int ERROR_CODE_ENCODING_FAILED = 15;
+
+    /** Caused by trying to encode content whose format is not supported. */
+    public static final int ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED = 16;
+
+    /** Caused by a video frame processing failure. */
+    public static final int ERROR_CODE_VIDEO_FRAME_PROCESSING_FAILED = 17;
+
+    /** Caused by an audio processing failure. */
+    public static final int ERROR_CODE_AUDIO_PROCESSING_FAILED = 18;
+
+    /** Caused by a failure while muxing media samples. */
+    public static final int ERROR_CODE_MUXING_FAILED = 19;
+
+    /** @hide */
+    @IntDef(
+            prefix = {"ERROR_CODE_"},
+            value = {
+                ERROR_CODE_NONE,
+                ERROR_CODE_FAILED_RUNTIME_CHECK,
+                ERROR_CODE_IO_UNSPECIFIED,
+                ERROR_CODE_IO_NETWORK_CONNECTION_FAILED,
+                ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT,
+                ERROR_CODE_IO_BAD_HTTP_STATUS,
+                ERROR_CODE_IO_FILE_NOT_FOUND,
+                ERROR_CODE_IO_NO_PERMISSION,
+                ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED,
+                ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE,
+                ERROR_CODE_DECODER_INIT_FAILED,
+                ERROR_CODE_DECODING_FAILED,
+                ERROR_CODE_DECODING_FORMAT_UNSUPPORTED,
+                ERROR_CODE_ENCODER_INIT_FAILED,
+                ERROR_CODE_ENCODING_FAILED,
+                ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED,
+                ERROR_CODE_VIDEO_FRAME_PROCESSING_FAILED,
+                ERROR_CODE_AUDIO_PROCESSING_FAILED,
+                ERROR_CODE_MUXING_FAILED,
+            })
+    @Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+    public @interface ErrorCode {}
+
+    private final @ErrorCode int mErrorCode;
+    @SuppressWarnings("HidingField") // Hiding field from superclass as for playback events.
+    private final long mTimeSinceCreatedMillis;
+
+    private EditingEndedEvent(
+            @FinalState int finalState,
+            @ErrorCode int errorCode,
+            long timeSinceCreatedMillis,
+            @NonNull Bundle extras) {
+        mFinalState = finalState;
+        mErrorCode = errorCode;
+        mTimeSinceCreatedMillis = timeSinceCreatedMillis;
+        mMetricsBundle = extras.deepCopy();
+    }
+
+    /** Returns the state of the editing session when it ended. */
+    @FinalState
+    public int getFinalState() {
+        return mFinalState;
+    }
+
+    /** Returns the error code for a {@linkplain #FINAL_STATE_ERROR failed} editing session. */
+    @ErrorCode
+    public int getErrorCode() {
+        return mErrorCode;
+    }
+
+    /**
+     * Gets the elapsed time since creating of the editing session, in milliseconds, or -1 if
+     * unknown.
+     *
+     * @return The elapsed time since creating the editing session, in milliseconds, or -1 if
+     *     unknown.
+     * @see LogSessionId
+     * @see EditingSession
+     */
+    @Override
+    @IntRange(from = -1)
+    public long getTimeSinceCreatedMillis() {
+        return mTimeSinceCreatedMillis;
+    }
+
+    /**
+     * Gets metrics-related information that is not supported by dedicated methods.
+     *
+     * <p>It is intended to be used for backwards compatibility by the metrics infrastructure.
+     */
+    @Override
+    @NonNull
+    public Bundle getMetricsBundle() {
+        return mMetricsBundle;
+    }
+
+    @Override
+    @NonNull
+    public String toString() {
+        return "PlaybackErrorEvent { "
+                + "finalState = "
+                + mFinalState
+                + ", "
+                + "errorCode = "
+                + mErrorCode
+                + ", "
+                + "timeSinceCreatedMillis = "
+                + mTimeSinceCreatedMillis
+                + " }";
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        EditingEndedEvent that = (EditingEndedEvent) o;
+        return mFinalState == that.mFinalState
+                && mErrorCode == that.mErrorCode
+                && mTimeSinceCreatedMillis == that.mTimeSinceCreatedMillis;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mFinalState, mErrorCode, mTimeSinceCreatedMillis);
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mFinalState);
+        dest.writeInt(mErrorCode);
+        dest.writeLong(mTimeSinceCreatedMillis);
+        dest.writeBundle(mMetricsBundle);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    private EditingEndedEvent(@NonNull Parcel in) {
+        int finalState = in.readInt();
+        int errorCode = in.readInt();
+        long timeSinceCreatedMillis = in.readLong();
+        Bundle metricsBundle = in.readBundle();
+
+        mFinalState = finalState;
+        mErrorCode = errorCode;
+        mTimeSinceCreatedMillis = timeSinceCreatedMillis;
+        mMetricsBundle = metricsBundle;
+    }
+
+    public static final @NonNull Creator<EditingEndedEvent> CREATOR =
+            new Creator<>() {
+                @Override
+                public EditingEndedEvent[] newArray(int size) {
+                    return new EditingEndedEvent[size];
+                }
+
+                @Override
+                public EditingEndedEvent createFromParcel(@NonNull Parcel in) {
+                    return new EditingEndedEvent(in);
+                }
+            };
+
+    /** Builder for {@link EditingEndedEvent} */
+    @FlaggedApi(FLAG_ADD_MEDIA_METRICS_EDITING)
+    public static final class Builder {
+        private final @FinalState int mFinalState;
+        private @ErrorCode int mErrorCode;
+        private long mTimeSinceCreatedMillis;
+        private Bundle mMetricsBundle;
+
+        /**
+         * Creates a new Builder.
+         *
+         * @param finalState The state of the editing session when it ended.
+         */
+        public Builder(@FinalState int finalState) {
+            mFinalState = finalState;
+            mErrorCode = ERROR_CODE_NONE;
+            mTimeSinceCreatedMillis = -1;
+            mMetricsBundle = new Bundle();
+        }
+
+        /**
+         * Sets the elapsed time since creating the editing session, in milliseconds.
+         *
+         * @param timeSinceCreatedMillis The elapsed time since creating the editing session, in
+         *     milliseconds, or -1 if the value is unknown.
+         * @see #getTimeSinceCreatedMillis()
+         */
+        public @NonNull Builder setTimeSinceCreatedMillis(
+                @IntRange(from = -1) long timeSinceCreatedMillis) {
+            mTimeSinceCreatedMillis = timeSinceCreatedMillis;
+            return this;
+        }
+
+        /** Sets the error code for a {@linkplain #FINAL_STATE_ERROR failed} editing session. */
+        public @NonNull Builder setErrorCode(@ErrorCode int value) {
+            mErrorCode = value;
+            return this;
+        }
+
+        /**
+         * Sets metrics-related information that is not supported by dedicated methods.
+         *
+         * <p>Used for backwards compatibility by the metrics infrastructure.
+         */
+        public @NonNull Builder setMetricsBundle(@NonNull Bundle metricsBundle) {
+            mMetricsBundle = metricsBundle;
+            return this;
+        }
+
+        /** Builds an instance. */
+        public @NonNull EditingEndedEvent build() {
+            return new EditingEndedEvent(
+                    mFinalState, mErrorCode, mTimeSinceCreatedMillis, mMetricsBundle);
+        }
+    }
+}
diff --git a/media/java/android/media/metrics/EditingSession.java b/media/java/android/media/metrics/EditingSession.java
index 2ddf623b..964e12c 100644
--- a/media/java/android/media/metrics/EditingSession.java
+++ b/media/java/android/media/metrics/EditingSession.java
@@ -16,6 +16,9 @@
 
 package android.media.metrics;
 
+import static com.android.media.editing.flags.Flags.FLAG_ADD_MEDIA_METRICS_EDITING;
+
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 
@@ -24,7 +27,8 @@
 import java.util.Objects;
 
 /**
- * An instances of this class represents a session of media editing.
+ * Represents a session of media editing, for example, transcoding between formats, transmuxing or
+ * applying trimming or audio/video effects to a stream.
  */
 public final class EditingSession implements AutoCloseable {
     private final @NonNull String mId;
@@ -40,6 +44,13 @@
         mLogSessionId = new LogSessionId(mId);
     }
 
+    /** Reports that an editing operation ended. */
+    @FlaggedApi(FLAG_ADD_MEDIA_METRICS_EDITING)
+    public void reportEditingEndedEvent(@NonNull EditingEndedEvent editingEndedEvent) {
+        mManager.reportEditingEndedEvent(mId, editingEndedEvent);
+    }
+
+    /** Returns the identifier for logging this session. */
     public @NonNull LogSessionId getSessionId() {
         return mLogSessionId;
     }
diff --git a/media/java/android/media/metrics/IMediaMetricsManager.aidl b/media/java/android/media/metrics/IMediaMetricsManager.aidl
index 51b1cc2..e07ca67 100644
--- a/media/java/android/media/metrics/IMediaMetricsManager.aidl
+++ b/media/java/android/media/metrics/IMediaMetricsManager.aidl
@@ -16,6 +16,7 @@
 
 package android.media.metrics;
 
+import android.media.metrics.EditingEndedEvent;
 import android.media.metrics.NetworkEvent;
 import android.media.metrics.PlaybackErrorEvent;
 import android.media.metrics.PlaybackMetrics;
@@ -24,7 +25,7 @@
 import android.os.PersistableBundle;
 
 /**
- * Interface to the playback manager service.
+ * Interface to the media metrics manager service.
  * @hide
  */
 interface IMediaMetricsManager {
@@ -37,6 +38,8 @@
     void reportPlaybackStateEvent(in String sessionId, in PlaybackStateEvent event, int userId);
     void reportTrackChangeEvent(in String sessionId, in TrackChangeEvent event, int userId);
 
+    void reportEditingEndedEvent(in String sessionId, in EditingEndedEvent event, int userId);
+
     String getTranscodingSessionId(int userId);
     String getEditingSessionId(int userId);
     String getBundleSessionId(int userId);
diff --git a/media/java/android/media/metrics/MediaMetricsManager.java b/media/java/android/media/metrics/MediaMetricsManager.java
index 0898874..622b0c1 100644
--- a/media/java/android/media/metrics/MediaMetricsManager.java
+++ b/media/java/android/media/metrics/MediaMetricsManager.java
@@ -193,4 +193,18 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Reports the event of an editing session ending.
+     *
+     * @hide
+     */
+    public void reportEditingEndedEvent(
+            @NonNull String sessionId, EditingEndedEvent editingEndedEvent) {
+        try {
+            mService.reportEditingEndedEvent(sessionId, editingEndedEvent, mUserId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
index e3dba03..7b58531 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
@@ -61,7 +61,8 @@
     void onSetTvRecordingInfo(in String recordingId, in TvRecordingInfo recordingInfo, int seq);
     void onRequestTvRecordingInfo(in String recordingId, int seq);
     void onRequestTvRecordingInfoList(in int type, int seq);
-    void onRequestSigning(
-            in String id, in String algorithm, in String alias, in byte[] data, int seq);
+    void onRequestSigning(in String id, in String algorithm, in String alias, in byte[] data,
+            int seq);
+    void onRequestCertificate(in String host, int port, int seq);
     void onAdRequest(in AdRequest request, int Seq);
 }
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
index 0f58b29..1b9450b 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
@@ -58,6 +58,8 @@
     void sendAvailableSpeeds(in IBinder sessionToken, in float[] speeds, int userId);
     void sendSigningResult(in IBinder sessionToken, in String signingId, in byte[] result,
             int userId);
+    void sendCertificate(in IBinder sessionToken, in String host, int port,
+            in Bundle certBundle, int userId);
     void sendTvRecordingInfo(in IBinder sessionToken, in TvRecordingInfo recordingInfo, int userId);
     void sendTvRecordingInfoList(in IBinder sessionToken,
             in List<TvRecordingInfo> recordingInfoList, int userId);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
index 06808c9..3969315 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
@@ -49,6 +49,7 @@
     void sendTimeShiftMode(int mode);
     void sendAvailableSpeeds(in float[] speeds);
     void sendSigningResult(in String signingId, in byte[] result);
+    void sendCertificate(in String host, int port, in Bundle certBundle);
     void sendTvRecordingInfo(in TvRecordingInfo recordingInfo);
     void sendTvRecordingInfoList(in List<TvRecordingInfo> recordingInfoList);
     void notifyError(in String errMsg, in Bundle params);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
index 416b8f1..cb89181 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
@@ -61,5 +61,6 @@
     void onRequestTvRecordingInfo(in String recordingId);
     void onRequestTvRecordingInfoList(in int type);
     void onRequestSigning(in String id, in String algorithm, in String alias, in byte[] data);
+    void onRequestCertificate(in String host, int port);
     void onAdRequest(in AdRequest request);
 }
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
index 77730aa..ec6c2bf 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
@@ -104,6 +104,7 @@
     private static final int DO_SEND_AVAILABLE_SPEEDS = 47;
     private static final int DO_SEND_SELECTED_TRACK_INFO = 48;
     private static final int DO_NOTIFY_VIDEO_FREEZE_UPDATED = 49;
+    private static final int DO_SEND_CERTIFICATE = 50;
 
     private final HandlerCaller mCaller;
     private Session mSessionImpl;
@@ -369,6 +370,13 @@
                 mSessionImpl.notifyVideoFreezeUpdated((Boolean) msg.obj);
                 break;
             }
+            case DO_SEND_CERTIFICATE: {
+                SomeArgs args = (SomeArgs) msg.obj;
+                mSessionImpl.sendCertificate((String) args.arg1, (Integer) args.arg2,
+                        (Bundle) args.arg3);
+                args.recycle();
+                break;
+            }
             default: {
                 Log.w(TAG, "Unhandled message code: " + msg.what);
                 break;
@@ -483,6 +491,12 @@
     }
 
     @Override
+    public void sendCertificate(@NonNull String host, int port, @NonNull Bundle certBundle) {
+        mCaller.executeOrSendMessage(
+                mCaller.obtainMessageOOO(DO_SEND_CERTIFICATE, host, port, certBundle));
+    }
+
+    @Override
     public void notifyError(@NonNull String errMsg, @NonNull Bundle params) {
         mCaller.executeOrSendMessage(
                 mCaller.obtainMessageOO(DO_NOTIFY_ERROR, errMsg, params));
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index 8a340f6..011744f 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -34,6 +34,7 @@
 import android.media.tv.TvRecordingInfo;
 import android.media.tv.TvTrackInfo;
 import android.net.Uri;
+import android.net.http.SslCertificate;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -656,6 +657,18 @@
             }
 
             @Override
+            public void onRequestCertificate(String host, int port, int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postRequestCertificate(host, port);
+                }
+            }
+
+            @Override
             public void onSessionStateChanged(int state, int err, int seq) {
                 synchronized (mSessionCallbackRecordMap) {
                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
@@ -1328,6 +1341,19 @@
             }
         }
 
+        void sendCertificate(@NonNull String host, int port, @NonNull SslCertificate cert) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.sendCertificate(mToken, host, port, SslCertificate.saveState(cert),
+                        mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         void notifyError(@NonNull String errMsg, @NonNull Bundle params) {
             if (mToken == null) {
                 Log.w(TAG, "The session has been already released");
@@ -2232,6 +2258,15 @@
             });
         }
 
+        void postRequestCertificate(String host, int port) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onRequestCertificate(mSession, host, port);
+                }
+            });
+        }
+
         void postRequestTvRecordingInfo(String recordingId) {
             mHandler.post(new Runnable() {
                 @Override
@@ -2574,6 +2609,17 @@
         }
 
         /**
+         * This is called when the service requests a SSL certificate for client validation.
+         *
+         * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
+         * @param host the host name of the SSL authentication server.
+         * @param port the port of the SSL authentication server. E.g., 443
+         * @hide
+         */
+        public void onRequestCertificate(Session session, String host, int port) {
+        }
+
+        /**
          * This is called when {@link TvInteractiveAppService.Session#notifySessionStateChanged} is
          * called.
          *
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 5247a0e..054b272 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -46,6 +46,7 @@
 import android.media.tv.TvView;
 import android.media.tv.interactive.TvInteractiveAppView.TvInteractiveAppCallback;
 import android.net.Uri;
+import android.net.http.SslCertificate;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
@@ -734,6 +735,17 @@
         }
 
         /**
+         * Receives the requested Certificate
+         *
+         * @param host the host name of the SSL authentication server.
+         * @param port the port of the SSL authentication server. E.g., 443
+         * @param cert the SSL certificate received.
+         * @hide
+         */
+        public void onCertificate(@NonNull String host, int port, @NonNull SslCertificate cert) {
+        }
+
+        /**
          * Called when the application sends information of an error.
          *
          * @param errMsg the message of the error.
@@ -1633,6 +1645,32 @@
         }
 
         /**
+         * Requests a SSL certificate for client validation.
+         *
+         * @param host the host name of the SSL authentication server.
+         * @param port the port of the SSL authentication server. E.g., 443
+         * @hide
+         */
+        public void requestCertificate(@NonNull String host, int port) {
+            executeOrPostRunnableOnMainThread(new Runnable() {
+                @MainThread
+                @Override
+                public void run() {
+                    try {
+                        if (DEBUG) {
+                            Log.d(TAG, "requestCertificate");
+                        }
+                        if (mSessionCallback != null) {
+                            mSessionCallback.onRequestCertificate(host, port);
+                        }
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "error in requestCertificate", e);
+                    }
+                }
+            });
+        }
+
+        /**
          * Sends an advertisement request to be processed by the related TV input.
          *
          * @param request The advertisement request
@@ -1725,6 +1763,11 @@
             onSigningResult(signingId, result);
         }
 
+        void sendCertificate(String host, int port, Bundle certBundle) {
+            SslCertificate cert = SslCertificate.restoreState(certBundle);
+            onCertificate(host, port, cert);
+        }
+
         void notifyError(String errMsg, Bundle params) {
             onError(errMsg, params);
         }
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 5bb61c2..3b29574 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -34,6 +34,7 @@
 import android.media.tv.interactive.TvInteractiveAppManager.Session.FinishedInputEventCallback;
 import android.media.tv.interactive.TvInteractiveAppManager.SessionCallback;
 import android.net.Uri;
+import android.net.http.SslCertificate;
 import android.os.Bundle;
 import android.os.Handler;
 import android.util.AttributeSet;
@@ -756,6 +757,22 @@
     }
 
     /**
+     * Send the requested SSL certificate to the TV Interactive App
+     * @param host the host name of the SSL authentication server.
+     * @param port the port of the SSL authentication server. E.g., 443
+     * @param cert the SSL certificate requested
+     * @hide
+     */
+    public void sendCertificate(@NonNull String host, int port, @NonNull SslCertificate cert) {
+        if (DEBUG) {
+            Log.d(TAG, "sendCertificate");
+        }
+        if (mSession != null) {
+            mSession.sendCertificate(host, port, cert);
+        }
+    }
+
+    /**
      * Notifies the corresponding {@link TvInteractiveAppService} when there is an error.
      *
      * @param errMsg the message of the error.
diff --git a/media/jni/soundpool/StreamManager.cpp b/media/jni/soundpool/StreamManager.cpp
index 52060f1..66fec1c 100644
--- a/media/jni/soundpool/StreamManager.cpp
+++ b/media/jni/soundpool/StreamManager.cpp
@@ -35,10 +35,9 @@
 // In R, we change this to true, as it is the correct way per SoundPool documentation.
 static constexpr bool kStealActiveStream_OldestFirst = true;
 
-// kPlayOnCallingThread = true prior to R.
 // Changing to false means calls to play() are almost instantaneous instead of taking around
 // ~10ms to launch the AudioTrack. It is perhaps 100x faster.
-static constexpr bool kPlayOnCallingThread = true;
+static constexpr bool kPlayOnCallingThread = false;
 
 // Amount of time for a StreamManager thread to wait before closing.
 static constexpr int64_t kWaitTimeBeforeCloseNs = 9 * NANOS_PER_SECOND;
diff --git a/media/jni/soundpool/StreamManager.h b/media/jni/soundpool/StreamManager.h
index adbab4b..340b49b 100644
--- a/media/jni/soundpool/StreamManager.h
+++ b/media/jni/soundpool/StreamManager.h
@@ -48,7 +48,7 @@
 public:
     JavaThread(std::function<void()> f, const char *name)
         : mF{std::move(f)} {
-        createThreadEtc(staticFunction, this, name);
+        createThreadEtc(staticFunction, this, name, ANDROID_PRIORITY_AUDIO);
     }
 
     JavaThread(JavaThread &&) = delete; // uses "this" ptr, not moveable.
diff --git a/media/jni/soundpool/android_media_SoundPool.cpp b/media/jni/soundpool/android_media_SoundPool.cpp
index 25040a9..e872a58 100644
--- a/media/jni/soundpool/android_media_SoundPool.cpp
+++ b/media/jni/soundpool/android_media_SoundPool.cpp
@@ -86,7 +86,7 @@
     }
 
     // Retrieves the associated object, returns nullValue T if not available.
-    T get(JNIEnv *env, jobject thiz) {
+    T get(JNIEnv *env, jobject thiz) const {
         std::lock_guard lg(mLock);
         // NOLINTNEXTLINE(performance-no-int-to-ptr)
         auto ptr = reinterpret_cast<T*>(env->GetLongField(thiz, mFieldId));
@@ -167,8 +167,10 @@
 //    is possible by checking if the WeakGlobalRef is null equivalent.
 
 auto& getSoundPoolManager() {
-    static ObjectManager<std::shared_ptr<SoundPool>> soundPoolManager(fields.mNativeContext);
-    return soundPoolManager;
+    // never-delete singleton
+    static auto soundPoolManager =
+            new ObjectManager<std::shared_ptr<SoundPool>>(fields.mNativeContext);
+    return *soundPoolManager;
 }
 
 inline auto getSoundPool(JNIEnv *env, jobject thiz) {
@@ -274,8 +276,9 @@
 auto& getSoundPoolJavaRefManager() {
     // Note this can store shared_ptrs to either jweak and jobject,
     // as the underlying type is identical.
-    static ConcurrentHashMap<SoundPool *, std::shared_ptr<JWeakValue>> concurrentHashMap;
-    return concurrentHashMap;
+    static auto concurrentHashMap =
+            new ConcurrentHashMap<SoundPool *, std::shared_ptr<JWeakValue>>();
+    return *concurrentHashMap;
 }
 
 // make_shared_globalref_from_localref() creates a sharable Java global
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 10c570b..8ea4632 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -72,6 +72,9 @@
             ],
         },
     },
+    stubs: {
+        symbol_file: "libjnigraphics.map.txt",
+    },
 }
 
 // The headers module is in frameworks/native/Android.bp.
@@ -93,15 +96,18 @@
     ],
     static_libs: ["libarect"],
     fuzz_config: {
-        cc: ["dichenzhang@google.com","scroggo@google.com"],
+        cc: [
+            "dichenzhang@google.com",
+            "scroggo@google.com",
+        ],
         asan_options: [
             "detect_odr_violation=1",
         ],
         hwasan_options: [
-             // Image decoders may attempt to allocate a large amount of memory
-             // (especially if the encoded image is large). This doesn't
-             // necessarily mean there is a bug. Set allocator_may_return_null=1
-             // for hwasan so the fuzzer can continue running.
+            // Image decoders may attempt to allocate a large amount of memory
+            // (especially if the encoded image is large). This doesn't
+            // necessarily mean there is a bug. Set allocator_may_return_null=1
+            // for hwasan so the fuzzer can continue running.
             "allocator_may_return_null = 1",
         ],
     },
diff --git a/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
index 5becc86..f13402c 100644
--- a/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
+++ b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
@@ -23,7 +23,7 @@
         android:shape="rectangle"
         android:top="1dp">
         <shape>
-            <corners android:radius="16dp" />
+            <corners android:radius="4dp" />
             <solid android:color="@color/dropdown_container" />
         </shape>
     </item>
diff --git a/packages/CredentialManager/res/drawable/more_options_list_item.xml b/packages/CredentialManager/res/drawable/more_options_list_item.xml
new file mode 100644
index 0000000..d7b509e
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/more_options_list_item.xml
@@ -0,0 +1,31 @@
+<?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.
+  -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:tools="http://schemas.android.com/tools" tools:ignore="NewApi"
+        android:color="@android:color/transparent">
+    <item
+        android:bottom="1dp"
+        android:shape="rectangle"
+        android:top="1dp">
+        <shape>
+            <corners android:bottomLeftRadius="4dp"
+                     android:bottomRightRadius="4dp"/>
+            <solid android:color="@color/sign_in_options_container" />
+        </shape>
+    </item>
+</ripple>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
new file mode 100644
index 0000000..929756c
--- /dev/null
+++ b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
@@ -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.
+  -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                android:id="@android:id/content"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
+                android:elevation="3dp">
+
+    <ImageView
+        android:id="@android:id/icon1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:layout_alignParentStart="true"
+        android:contentDescription="@string/provider_icon_content_description"
+        android:background="@null"/>
+    <TextView
+        android:id="@android:id/text1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:layout_toEndOf="@android:id/icon1"
+        android:minWidth="@dimen/autofill_dropdown_textview_min_width"
+        android:maxWidth="@dimen/autofill_dropdown_textview_max_width"
+        style="@style/autofill.TextTitle"/>
+
+</RelativeLayout>
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
index cb6c6b4..1fe5e0e 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
@@ -17,22 +17,25 @@
                 android:id="@android:id/content"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:maxWidth="@dimen/autofill_dropdown_layout_width"
+                android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
                 android:elevation="3dp">
 
         <ImageView
             android:id="@android:id/icon1"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
+            android:contentDescription="@string/provider_icon_content_description"
             android:layout_centerVertical="true"
             android:layout_alignParentStart="true"
             android:background="@null"/>
         <TextView
             android:id="@android:id/text1"
-            android:layout_width="@dimen/autofill_dropdown_text_width"
+            android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_alignParentTop="true"
             android:layout_toEndOf="@android:id/icon1"
+            android:minWidth="@dimen/autofill_dropdown_textview_min_width"
+            android:maxWidth="@dimen/autofill_dropdown_textview_max_width"
             style="@style/autofill.TextTitle"/>
         <TextView
             android:id="@android:id/text2"
@@ -40,6 +43,8 @@
             android:layout_height="wrap_content"
             android:layout_below="@android:id/text1"
             android:layout_toEndOf="@android:id/icon1"
+            android:minWidth="@dimen/autofill_dropdown_textview_min_width"
+            android:maxWidth="@dimen/autofill_dropdown_textview_max_width"
             style="@style/autofill.TextSubtitle"/>
 
 </RelativeLayout>
diff --git a/packages/CredentialManager/res/values/colors.xml b/packages/CredentialManager/res/values/colors.xml
index dcb7ef9..7cb1d01 100644
--- a/packages/CredentialManager/res/values/colors.xml
+++ b/packages/CredentialManager/res/values/colors.xml
@@ -20,4 +20,6 @@
     <color name="text_primary">#1A1B20</color>
     <color name="text_secondary">#44474F</color>
     <color name="dropdown_container">#F3F3FA</color>
+    <color name="sign_in_options_container">#DADADA</color>
+    <color name="sign_in_options_icon_color">#1B1B1B</color>
 </resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/dimens.xml b/packages/CredentialManager/res/values/dimens.xml
index 2a4719d..3a8c78f 100644
--- a/packages/CredentialManager/res/values/dimens.xml
+++ b/packages/CredentialManager/res/values/dimens.xml
@@ -18,11 +18,13 @@
 
 <resources>
     <dimen name="autofill_view_top_padding">12dp</dimen>
-    <dimen name="autofill_view_right_padding">24dp</dimen>
+    <dimen name="autofill_view_right_padding">12dp</dimen>
     <dimen name="autofill_view_bottom_padding">12dp</dimen>
     <dimen name="autofill_view_left_padding">16dp</dimen>
     <dimen name="autofill_view_icon_to_text_padding">10dp</dimen>
     <dimen name="autofill_icon_size">24dp</dimen>
-    <dimen name="autofill_dropdown_layout_width">296dp</dimen>
-    <dimen name="autofill_dropdown_text_width">240dp</dimen>
+    <dimen name="autofill_dropdown_textview_min_width">112dp</dimen>
+    <dimen name="autofill_dropdown_textview_max_width">230dp</dimen>
+    <dimen name="dropdown_layout_horizontal_margin">24dp</dimen>
+    <integer name="autofill_max_visible_datasets">3</integer>
 </resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 605e77b..f98164b 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -168,4 +168,9 @@
   <string name="get_dialog_option_headline_use_a_different_device">Use a different device</string>
   <!-- Text shown on a snackbar when the app cancelled the UI. [CHAR LIMIT=120] -->
   <string name="request_cancelled_by">Request cancelled by <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
+
+  <!-- Strings for dropdown presentation. -->
+  <!-- Text shown in the dropdown presentation to select more sign in options. [CHAR LIMIT=120] -->
+  <string name="dropdown_presentation_more_sign_in_options_text">Sign-in options</string>
+  <string name="provider_icon_content_description">Credential provider icon</string>
 </resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 03ac605..985f322 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -30,6 +30,7 @@
 import android.os.Bundle
 import android.os.CancellationSignal
 import android.os.OutcomeReceiver
+import android.provider.Settings
 import android.credentials.Credential
 import android.service.autofill.AutofillService
 import android.service.autofill.Dataset
@@ -48,7 +49,9 @@
 import android.view.autofill.AutofillId
 import android.widget.inline.InlinePresentationSpec
 import android.credentials.CredentialManager
+import android.widget.RemoteViews
 import androidx.autofill.inline.v1.InlineSuggestionUi
+import androidx.core.content.ContextCompat
 import androidx.credentials.provider.CustomCredentialEntry
 import androidx.credentials.provider.PasswordCredentialEntry
 import androidx.credentials.provider.PublicKeyCredentialEntry
@@ -115,7 +118,7 @@
         }
 
         val getCredRequest: GetCredentialRequest? = getCredManRequest(structure, sessionId,
-            requestId)
+                requestId)
         if (getCredRequest == null) {
             Log.i(TAG, "No credential manager request found")
             callback.onFailure("No credential manager request found")
@@ -307,10 +310,14 @@
         val inlineMaxSuggestedCount = inlineSuggestionsRequest?.maxSuggestionCount ?: 0
         val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs
         val inlinePresentationSpecsCount = inlinePresentationSpecs?.size ?: 0
-        var maxItemCount = totalEntryCount
-        if (inlineMaxSuggestedCount > 0) {
-            maxItemCount = maxItemCount.coerceAtMost(inlineMaxSuggestedCount)
-        }
+        val maxDropdownDisplayLimit = this.resources.getInteger(
+                com.android.credentialmanager.R.integer.autofill_max_visible_datasets)
+        var maxInlineItemCount = totalEntryCount
+        maxInlineItemCount = maxInlineItemCount.coerceAtMost(inlineMaxSuggestedCount)
+        val lastDropdownDatasetIndex = Settings.Global.getInt(this.contentResolver,
+                Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS,
+                (maxDropdownDisplayLimit - 1).coerceAtMost(totalEntryCount - 1))
+
         var i = 0
         var datasetAdded = false
 
@@ -333,13 +340,8 @@
                 Log.e(TAG, "PendingIntent was missing from the entry.")
                 return@usernameLoop
             }
-            if (inlinePresentationSpecs == null) {
-                Log.i(TAG, "Inline presentation spec is null, " +
-                        "building dropdown presentation only")
-            }
-            if (i >= maxItemCount) {
-                Log.e(TAG, "Skipping because reached the max item count.")
-                return@usernameLoop
+            if (i >= maxInlineItemCount && i >= lastDropdownDatasetIndex) {
+                return@usernameLoop;
             }
             val icon: Icon = if (primaryEntry.icon == null) {
                 // The empty entry icon has non-null icon reference but null drawable reference.
@@ -351,38 +353,26 @@
             }
             // Create inline presentation
             var inlinePresentation: InlinePresentation? = null
-            var spec: InlinePresentationSpec?
-            if (inlinePresentationSpecs != null) {
-                if (i < inlinePresentationSpecsCount) {
-                    spec = inlinePresentationSpecs[i]
+            if (inlinePresentationSpecs != null && i < maxInlineItemCount) {
+                val spec: InlinePresentationSpec? = if (i < inlinePresentationSpecsCount) {
+                    inlinePresentationSpecs[i]
                 } else {
-                    spec = inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
+                    inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
                 }
-                val displayName: String = if (primaryEntry.credentialType ==
-                        CredentialType.PASSKEY && primaryEntry.displayName != null) {
-                    primaryEntry.displayName!!
-                } else {
-                    primaryEntry.userName
-                }
-                val sliceBuilder = InlineSuggestionUi
-                        .newContentBuilder(pendingIntent)
-                        .setTitle(displayName)
-                sliceBuilder.setStartIcon(icon)
-                if (primaryEntry.credentialType ==
-                        CredentialType.PASSKEY && duplicateDisplayNamesForPasskeys[displayName]
-                        == true) {
-                    sliceBuilder.setSubtitle(primaryEntry.userName)
-                }
-                inlinePresentation = InlinePresentation(
-                        sliceBuilder.build().slice, spec, /* pinned= */ false)
+                inlinePresentation = createInlinePresentation(primaryEntry, pendingIntent, icon,
+                        spec!!, duplicateDisplayNamesForPasskeys)
             }
-            val dropdownPresentation = RemoteViewsFactory.createDropdownPresentation(
-                    this, icon, primaryEntry)
-            i++
+            var dropdownPresentation: RemoteViews? = null
+            if (i < lastDropdownDatasetIndex) {
+                dropdownPresentation = RemoteViewsFactory
+                        .createDropdownPresentation(this, icon, primaryEntry)
+            }
 
             val dataSetBuilder = Dataset.Builder()
             val presentationBuilder = Presentations.Builder()
-                    .setMenuPresentation(dropdownPresentation)
+            if (dropdownPresentation != null) {
+                presentationBuilder.setMenuPresentation(dropdownPresentation)
+            }
             if (inlinePresentation != null) {
                 presentationBuilder.setInlinePresentation(inlinePresentation)
             }
@@ -398,6 +388,12 @@
                             .setAuthenticationExtras(fillInIntent.extras)
                             .build())
             datasetAdded = true
+            i++
+
+            if (i == lastDropdownDatasetIndex && bottomSheetPendingIntent != null) {
+                addDropdownMoreOptionsPresentation(bottomSheetPendingIntent, autofillId,
+                        fillResponseBuilder)
+            }
         }
         val pinnedSpec = getLastInlinePresentationSpec(inlinePresentationSpecs,
                 inlinePresentationSpecsCount)
@@ -408,6 +404,49 @@
         return datasetAdded
     }
 
+    private fun createInlinePresentation(primaryEntry: CredentialEntryInfo,
+                                         pendingIntent: PendingIntent,
+                                         icon: Icon,
+                                         spec: InlinePresentationSpec,
+                                         duplicateDisplayNameForPasskeys: MutableMap<String, Boolean>):
+            InlinePresentation {
+        val displayName: String = if (primaryEntry.credentialType == CredentialType.PASSKEY
+                && primaryEntry.displayName != null) {
+            primaryEntry.displayName!!
+        } else {
+            primaryEntry.userName
+        }
+        val sliceBuilder = InlineSuggestionUi
+                .newContentBuilder(pendingIntent)
+                .setTitle(displayName)
+        sliceBuilder.setStartIcon(icon)
+        if (primaryEntry.credentialType ==
+                CredentialType.PASSKEY && duplicateDisplayNameForPasskeys[displayName] == true) {
+            sliceBuilder.setSubtitle(primaryEntry.userName)
+        }
+        return InlinePresentation(
+                sliceBuilder.build().slice, spec, /* pinned= */ false)
+    }
+
+    private fun addDropdownMoreOptionsPresentation(
+            bottomSheetPendingIntent: PendingIntent,
+            autofillId: AutofillId,
+            fillResponseBuilder: FillResponse.Builder) {
+        val presentationBuilder = Presentations.Builder()
+                .setMenuPresentation(RemoteViewsFactory.createMoreSignInOptionsPresentation(this))
+
+        fillResponseBuilder.addDataset(
+                Dataset.Builder()
+                        .setField(
+                                autofillId,
+                                Field.Builder().setPresentations(
+                                        presentationBuilder.build())
+                                        .build())
+                        .setAuthentication(bottomSheetPendingIntent.intentSender)
+                        .build()
+        )
+    }
+
     private fun getLastInlinePresentationSpec(
             inlinePresentationSpecs: List<InlinePresentationSpec>?,
             inlinePresentationSpecsCount: Int
@@ -534,9 +573,9 @@
     }
 
     private fun getCredManRequest(
-        structure: AssistStructure,
-        sessionId: Int,
-        requestId: Int
+            structure: AssistStructure,
+            sessionId: Int,
+            requestId: Int
     ): GetCredentialRequest? {
         val credentialOptions: MutableList<CredentialOption> = mutableListOf()
         traverseStructure(structure, credentialOptions)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
index e039dea..68f1c86 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
@@ -44,7 +44,7 @@
             if (credentialEntryInfo.credentialType == CredentialType.UNKNOWN) {
                 return remoteViews
             }
-            setRemoteViewsPaddings(remoteViews, context)
+            setRemoteViewsPaddings(remoteViews, context, /* primaryTextBottomPadding=*/0)
             if (credentialEntryInfo.credentialType == CredentialType.PASSKEY) {
                 val displayName = credentialEntryInfo.displayName ?: credentialEntryInfo.userName
                 remoteViews.setTextViewText(android.R.id.text1, displayName)
@@ -81,8 +81,46 @@
             return remoteViews
         }
 
+        fun createMoreSignInOptionsPresentation(context: Context): RemoteViews {
+            var layoutId: Int = com.android.credentialmanager.R.layout
+                    .credman_dropdown_bottom_sheet
+            val remoteViews = RemoteViews(context.packageName, layoutId)
+            setRemoteViewsPaddings(remoteViews, context)
+            remoteViews.setTextViewText(android.R.id.text1, ContextCompat.getString(context,
+                    com.android.credentialmanager
+                            .R.string.dropdown_presentation_more_sign_in_options_text))
+
+            val textColorPrimary = ContextCompat.getColor(context,
+                    com.android.credentialmanager.R.color.text_primary)
+            remoteViews.setTextColor(android.R.id.text1, textColorPrimary)
+            val icon = Icon.createWithResource(context, com
+                    .android.credentialmanager.R.drawable.more_horiz_24px)
+            icon.setTint(ContextCompat.getColor(context,
+                    com.android.credentialmanager.R.color.sign_in_options_icon_color))
+            remoteViews.setImageViewIcon(android.R.id.icon1, icon)
+            remoteViews.setBoolean(
+                    android.R.id.icon1, setAdjustViewBoundsMethodName, true);
+            remoteViews.setInt(
+                    android.R.id.icon1,
+                    setMaxHeightMethodName,
+                    context.resources.getDimensionPixelSize(
+                            com.android.credentialmanager.R.dimen.autofill_icon_size));
+            val drawableId =
+                    com.android.credentialmanager.R.drawable.more_options_list_item
+            remoteViews.setInt(
+                    android.R.id.content, setBackgroundResourceMethodName, drawableId);
+            return remoteViews
+        }
+
         private fun setRemoteViewsPaddings(
                 remoteViews: RemoteViews, context: Context) {
+            val bottomPadding = context.resources.getDimensionPixelSize(
+                    com.android.credentialmanager.R.dimen.autofill_view_bottom_padding)
+            setRemoteViewsPaddings(remoteViews, context, bottomPadding)
+        }
+
+        private fun setRemoteViewsPaddings(
+                remoteViews: RemoteViews, context: Context, primaryTextBottomPadding: Int) {
             val leftPadding = context.resources.getDimensionPixelSize(
                     com.android.credentialmanager.R.dimen.autofill_view_left_padding)
             val iconToTextPadding = context.resources.getDimensionPixelSize(
@@ -104,7 +142,7 @@
                     iconToTextPadding,
                     /* top=*/topPadding,
                     /* right=*/rightPadding,
-                    /* bottom=*/0)
+                    primaryTextBottomPadding)
             remoteViews.setViewPadding(
                     android.R.id.text2,
                     iconToTextPadding,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
index 81a8b324..cea3d13 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
@@ -37,6 +37,7 @@
 interface AppRepository {
     fun loadLabel(app: ApplicationInfo): String
 
+    @Suppress("ABSTRACT_COMPOSABLE_DEFAULT_PARAMETER_VALUE")
     @Composable
     fun produceLabel(app: ApplicationInfo, isClonedAppPage: Boolean = false): State<String> {
         val context = LocalContext.current
diff --git a/packages/SettingsLib/res/drawable/ic_external_display.xml b/packages/SettingsLib/res/drawable/ic_external_display.xml
new file mode 100644
index 0000000..de50de8
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_external_display.xml
@@ -0,0 +1,28 @@
+<!--
+  ~ 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="25dp"
+    android:viewportWidth="24"
+    android:viewportHeight="25">
+  <group>
+    <clip-path
+        android:pathData="M0,0.307h24v24h-24z"/>
+    <path
+        android:pathData="M8,21.307V19.307H10V17.307H4C3.45,17.307 2.975,17.115 2.575,16.732C2.192,16.332 2,15.857 2,15.307V5.307C2,4.757 2.192,4.29 2.575,3.907C2.975,3.507 3.45,3.307 4,3.307H20C20.55,3.307 21.017,3.507 21.4,3.907C21.8,4.29 22,4.757 22,5.307V15.307C22,15.857 21.8,16.332 21.4,16.732C21.017,17.115 20.55,17.307 20,17.307H14V19.307H16V21.307H8ZM4,15.307H20V5.307H4V15.307ZM4,15.307V5.307V15.307Z"
+        android:fillColor="#E5E3D6"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
index cf4d6be..0613676 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
@@ -99,6 +99,8 @@
                 device.refresh();
             }
 
+            // Check current list of CachedDevices to see if any are hearing aid devices.
+            mDeviceManager.updateHearingAidsDevices();
             mIsProfileReady = true;
             mProfileManager.callServiceConnectedListeners();
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
index 3a15b71..9fd174d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
@@ -15,8 +15,8 @@
  */
 package com.android.settingslib.bluetooth;
 
-import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothLeAudio;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
 import android.bluetooth.le.ScanFilter;
@@ -68,14 +68,9 @@
 
     void initHearingAidDeviceIfNeeded(CachedBluetoothDevice newDevice,
             List<ScanFilter> leScanFilters) {
-        long hiSyncId = getHiSyncId(newDevice.getDevice());
-        if (isValidHiSyncId(hiSyncId)) {
-            // Once hiSyncId is valid, assign hearing aid info
-            final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder()
-                    .setAshaDeviceSide(getDeviceSide(newDevice.getDevice()))
-                    .setAshaDeviceMode(getDeviceMode(newDevice.getDevice()))
-                    .setHiSyncId(hiSyncId);
-            newDevice.setHearingAidInfo(infoBuilder.build());
+        HearingAidInfo info = generateHearingAidInfo(newDevice);
+        if (info != null) {
+            newDevice.setHearingAidInfo(info);
         } else if (leScanFilters != null && !newDevice.isHearingAidDevice()) {
             // If the device is added with hearing aid scan filter during pairing, set an empty
             // hearing aid info to indicate it's a hearing aid device. The info will be updated
@@ -94,38 +89,6 @@
         }
     }
 
-    private long getHiSyncId(BluetoothDevice device) {
-        final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
-        final HearingAidProfile profileProxy = profileManager.getHearingAidProfile();
-        if (profileProxy == null) {
-            return BluetoothHearingAid.HI_SYNC_ID_INVALID;
-        }
-
-        return profileProxy.getHiSyncId(device);
-    }
-
-    private int getDeviceSide(BluetoothDevice device) {
-        final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
-        final HearingAidProfile profileProxy = profileManager.getHearingAidProfile();
-        if (profileProxy == null) {
-            Log.w(TAG, "HearingAidProfile is not supported and not ready to fetch device side");
-            return HearingAidProfile.DeviceSide.SIDE_INVALID;
-        }
-
-        return profileProxy.getDeviceSide(device);
-    }
-
-    private int getDeviceMode(BluetoothDevice device) {
-        final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
-        final HearingAidProfile profileProxy = profileManager.getHearingAidProfile();
-        if (profileProxy == null) {
-            Log.w(TAG, "HearingAidProfile is not supported and not ready to fetch device mode");
-            return HearingAidProfile.DeviceMode.MODE_INVALID;
-        }
-
-        return profileProxy.getDeviceMode(device);
-    }
-
     boolean setSubDeviceIfNeeded(CachedBluetoothDevice newDevice) {
         final long hiSyncId = newDevice.getHiSyncId();
         if (isValidHiSyncId(hiSyncId)) {
@@ -157,21 +120,17 @@
 
     // To collect all HearingAid devices and call #onHiSyncIdChanged to group device by HiSyncId
     void updateHearingAidsDevices() {
-        final Set<Long> newSyncIdSet = new HashSet<Long>();
+        final Set<Long> newSyncIdSet = new HashSet<>();
         for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
             // Do nothing if HiSyncId has been assigned
-            if (!isValidHiSyncId(cachedDevice.getHiSyncId())) {
-                final long newHiSyncId = getHiSyncId(cachedDevice.getDevice());
-                // Do nothing if there is no HiSyncId on Bluetooth device
-                if (isValidHiSyncId(newHiSyncId)) {
-                    // Once hiSyncId is valid, assign hearing aid info
-                    final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder()
-                            .setAshaDeviceSide(getDeviceSide(cachedDevice.getDevice()))
-                            .setAshaDeviceMode(getDeviceMode(cachedDevice.getDevice()))
-                            .setHiSyncId(newHiSyncId);
-                    cachedDevice.setHearingAidInfo(infoBuilder.build());
-
-                    newSyncIdSet.add(newHiSyncId);
+            if (isValidHiSyncId(cachedDevice.getHiSyncId())) {
+                continue;
+            }
+            HearingAidInfo info = generateHearingAidInfo(cachedDevice);
+            if (info != null) {
+                cachedDevice.setHearingAidInfo(info);
+                if (isValidHiSyncId(info.getHiSyncId())) {
+                    newSyncIdSet.add(info.getHiSyncId());
                 }
             }
         }
@@ -378,6 +337,54 @@
         return null;
     }
 
+    private boolean isLeAudioHearingAid(CachedBluetoothDevice cachedDevice) {
+        List<LocalBluetoothProfile> profiles = cachedDevice.getProfiles();
+        boolean supportLeAudio = profiles.stream().anyMatch(p -> p instanceof LeAudioProfile);
+        boolean supportHapClient = profiles.stream().anyMatch(p -> p instanceof HapClientProfile);
+        return supportLeAudio && supportHapClient;
+    }
+
+    private boolean isAshaHearingAid(CachedBluetoothDevice cachedDevice) {
+        return cachedDevice.getProfiles().stream().anyMatch(p -> p instanceof HearingAidProfile);
+    }
+
+    private HearingAidInfo generateHearingAidInfo(CachedBluetoothDevice cachedDevice) {
+        final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
+        if (isAshaHearingAid(cachedDevice)) {
+            final HearingAidProfile asha = profileManager.getHearingAidProfile();
+            if (asha == null) {
+                Log.w(TAG, "HearingAidProfile is not supported on this device");
+            } else {
+                long hiSyncId = asha.getHiSyncId(cachedDevice.getDevice());
+                if (isValidHiSyncId(hiSyncId)) {
+                    final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder()
+                            .setAshaDeviceSide(asha.getDeviceSide(cachedDevice.getDevice()))
+                            .setAshaDeviceMode(asha.getDeviceMode(cachedDevice.getDevice()))
+                            .setHiSyncId(hiSyncId);
+                    return infoBuilder.build();
+                }
+            }
+        }
+        if (isLeAudioHearingAid(cachedDevice)) {
+            final HapClientProfile hapClientProfile = profileManager.getHapClientProfile();
+            final LeAudioProfile leAudioProfile = profileManager.getLeAudioProfile();
+            if (hapClientProfile == null || leAudioProfile == null) {
+                Log.w(TAG, "HapClientProfile or LeAudioProfile is not supported on this device");
+            } else {
+                int audioLocation = leAudioProfile.getAudioLocation(cachedDevice.getDevice());
+                int hearingAidType = hapClientProfile.getHearingAidType(cachedDevice.getDevice());
+                if (audioLocation != BluetoothLeAudio.AUDIO_LOCATION_INVALID
+                        && hearingAidType != HapClientProfile.HearingAidType.TYPE_INVALID) {
+                    final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder()
+                            .setLeAudioLocation(audioLocation)
+                            .setHapDeviceType(hearingAidType);
+                    return infoBuilder.build();
+                }
+            }
+        }
+        return null;
+    }
+
     private void log(String msg) {
         if (DEBUG) {
             Log.d(TAG, msg);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
index 14fab16..f2450de 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
@@ -109,7 +109,7 @@
                 device.refresh();
             }
 
-            // Check current list of CachedDevices to see if any are Hearing Aid devices.
+            // Check current list of CachedDevices to see if any are hearing aid devices.
             mDeviceManager.updateHearingAidsDevices();
             mIsProfileReady = true;
             mProfileManager.callServiceConnectedListeners();
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
index 931a6f1..6be4336 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
@@ -83,6 +83,8 @@
                 device.refresh();
             }
 
+            // Check current list of CachedDevices to see if any are hearing aid devices.
+            mDeviceManager.updateHearingAidsDevices();
             mProfileManager.callServiceConnectedListeners();
             mIsProfileReady = true;
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 9348705..1d2f790 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -759,6 +759,20 @@
     }
 
     /**
+     * Update the LE Broadcast by calling {@link BluetoothLeBroadcast#updateBroadcast(int,
+     * BluetoothLeAudioContentMetadata)}, currently only updates programInfo.
+     */
+    public void updateBroadcast() {
+        if (mServiceBroadcast == null) {
+            Log.d(TAG, "The BluetoothLeBroadcast is null when updating the broadcast.");
+            return;
+        }
+        String programInfo = getProgramInfo();
+        mBluetoothLeAudioContentMetadata = mBuilder.setProgramInfo(programInfo).build();
+        mServiceBroadcast.updateBroadcast(mBroadcastId, mBluetoothLeAudioContentMetadata);
+    }
+
+    /**
      * Register Broadcast Callbacks to track its state and receivers
      *
      * @param executor Executor object for callback
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
index cf224dc..3de4933 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
@@ -71,15 +71,15 @@
                         new Device(
                                 AudioDeviceInfo.TYPE_HDMI,
                                 MediaRoute2Info.TYPE_HDMI,
-                                mIsTv ? R.drawable.ic_tv : R.drawable.ic_headphone),
+                                mIsTv ? R.drawable.ic_tv : R.drawable.ic_external_display),
                         new Device(
                                 AudioDeviceInfo.TYPE_HDMI_ARC,
                                 MediaRoute2Info.TYPE_HDMI_ARC,
-                                mIsTv ? R.drawable.ic_hdmi : R.drawable.ic_headphone),
+                                mIsTv ? R.drawable.ic_hdmi : R.drawable.ic_external_display),
                         new Device(
                                 AudioDeviceInfo.TYPE_HDMI_EARC,
                                 MediaRoute2Info.TYPE_HDMI_EARC,
-                                mIsTv ? R.drawable.ic_hdmi : R.drawable.ic_headphone),
+                                mIsTv ? R.drawable.ic_hdmi : R.drawable.ic_external_display),
                         new Device(
                                 AudioDeviceInfo.TYPE_WIRED_HEADSET,
                                 MediaRoute2Info.TYPE_WIRED_HEADSET,
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
index 8a122fc..aef09ac 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
@@ -26,11 +26,13 @@
 import android.media.RouteDiscoveryPreference;
 import android.media.RouteListingPreference;
 import android.media.RoutingSessionInfo;
+import android.os.Process;
 import android.text.TextUtils;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.media.flags.Flags;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 
 import java.util.ArrayList;
@@ -62,21 +64,33 @@
                 refreshDevices();
             };
 
-    // TODO: b/192657812 - Create factory method in InfoMediaManager to return
-    //      RouterInfoMediaManager or ManagerInfoMediaManager based on flag.
+    // TODO (b/321969740): Plumb target UserHandle between UMO and RouterInfoMediaManager.
     public RouterInfoMediaManager(
             Context context,
             String packageName,
             Notification notification,
-            LocalBluetoothManager localBluetoothManager) throws PackageNotAvailableException {
+            LocalBluetoothManager localBluetoothManager)
+            throws PackageNotAvailableException {
         super(context, packageName, notification, localBluetoothManager);
 
-        mRouter = MediaRouter2.getInstance(context, packageName);
+        MediaRouter2 router = null;
 
-        if (mRouter == null) {
+        if (Flags.enableCrossUserRoutingInMediaRouter2()) {
+            try {
+                router = MediaRouter2.getInstance(context, packageName, Process.myUserHandle());
+            } catch (IllegalArgumentException ex) {
+                // Do nothing
+            }
+        } else {
+            router = MediaRouter2.getInstance(context, packageName);
+        }
+        if (router == null) {
             throw new PackageNotAvailableException(
                     "Package name " + packageName + " does not exist.");
         }
+        // We have to defer initialization because mRouter is final.
+        mRouter = router;
+
         mRouterManager = MediaRouter2Manager.getInstance(context);
     }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
index e7487e8..aa5a298 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
@@ -15,6 +15,15 @@
  */
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothHearingAid.HI_SYNC_ID_INVALID;
+import static android.bluetooth.BluetoothLeAudio.AUDIO_LOCATION_FRONT_LEFT;
+import static android.bluetooth.BluetoothLeAudio.AUDIO_LOCATION_INVALID;
+
+import static com.android.settingslib.bluetooth.HapClientProfile.HearingAidType.TYPE_BINAURAL;
+import static com.android.settingslib.bluetooth.HapClientProfile.HearingAidType.TYPE_INVALID;
+import static com.android.settingslib.bluetooth.HearingAidProfile.DeviceMode.MODE_BINAURAL;
+import static com.android.settingslib.bluetooth.HearingAidProfile.DeviceSide.SIDE_RIGHT;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -32,7 +41,6 @@
 
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHearingAid;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
 import android.bluetooth.le.ScanFilter;
@@ -92,6 +100,10 @@
     @Mock
     private HearingAidProfile mHearingAidProfile;
     @Mock
+    private LeAudioProfile mLeAudioProfile;
+    @Mock
+    private HapClientProfile mHapClientProfile;
+    @Mock
     private AudioProductStrategy mAudioStrategy;
     @Mock
     private BluetoothDevice mDevice1;
@@ -123,6 +135,8 @@
         when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager);
         when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalProfileManager);
         when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);
+        when(mLocalProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
+        when(mLocalProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile);
         when(mAudioStrategy.getAudioAttributesForLegacyStreamType(
                 AudioManager.STREAM_MUSIC))
                 .thenReturn((new AudioAttributes.Builder()).build());
@@ -140,34 +154,43 @@
     }
 
     /**
-     * Test initHearingAidDeviceIfNeeded, set HearingAid's information, including HiSyncId,
-     * deviceSide, deviceMode.
+     * Test initHearingAidDeviceIfNeeded
+     *
+     * Conditions:
+     *      1) ASHA hearing aid
+     *      2) Valid HiSyncId
+     * Result:
+     *      Set hearing aid info to the device.
      */
     @Test
-    public void initHearingAidDeviceIfNeeded_validHiSyncId_setHearingAidInfo() {
+    public void initHearingAidDeviceIfNeeded_asha_validHiSyncId_setHearingAidInfo() {
+        when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
         when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
-        when(mHearingAidProfile.getDeviceMode(mDevice1)).thenReturn(
-                HearingAidProfile.DeviceMode.MODE_BINAURAL);
-        when(mHearingAidProfile.getDeviceSide(mDevice1)).thenReturn(
-                HearingAidProfile.DeviceSide.SIDE_RIGHT);
+        when(mHearingAidProfile.getDeviceMode(mDevice1)).thenReturn(MODE_BINAURAL);
+        when(mHearingAidProfile.getDeviceSide(mDevice1)).thenReturn(SIDE_RIGHT);
 
         assertThat(mCachedDevice1.getHiSyncId()).isNotEqualTo(HISYNCID1);
         mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, null);
 
         assertThat(mCachedDevice1.getHiSyncId()).isEqualTo(HISYNCID1);
+        assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_RIGHT);
         assertThat(mCachedDevice1.getDeviceMode()).isEqualTo(
                 HearingAidInfo.DeviceMode.MODE_BINAURAL);
-        assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(
-                HearingAidInfo.DeviceSide.SIDE_RIGHT);
     }
 
     /**
-     * Test initHearingAidDeviceIfNeeded, an invalid HiSyncId will not be assigned
+     * Test initHearingAidDeviceIfNeeded
+     *
+     * Conditions:
+     *      1) ASHA hearing aid
+     *      2) Invalid HiSyncId
+     * Result:
+     *      Do not set hearing aid info to the device.
      */
     @Test
-    public void initHearingAidDeviceIfNeeded_invalidHiSyncId_notToSetHearingAidInfo() {
-        when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(
-                BluetoothHearingAid.HI_SYNC_ID_INVALID);
+    public void initHearingAidDeviceIfNeeded_asha_invalidHiSyncId_notToSetHearingAidInfo() {
+        when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+        when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HI_SYNC_ID_INVALID);
 
         mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, null);
 
@@ -175,34 +198,89 @@
     }
 
     /**
-     * Test initHearingAidDeviceIfNeeded, an invalid HiSyncId and hearing aid scan filter, set an
-     * empty hearing aid info on the device.
+     * Test initHearingAidDeviceIfNeeded
+     *
+     * Conditions:
+     *      1) ASHA hearing aid
+     *      2) Invalid HiSyncId
+     *      3) ASHA uuid scan filter
+     * Result:
+     *      Set an empty hearing aid info to the device.
      */
     @Test
-    public void initHearingAidDeviceIfNeeded_hearingAidScanFilter_setHearingAidInfo() {
-        when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(
-                BluetoothHearingAid.HI_SYNC_ID_INVALID);
+    public void initHearingAidDeviceIfNeeded_asha_scanFilterNotNull_setEmptyHearingAidInfo() {
+        when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+        when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HI_SYNC_ID_INVALID);
         final ScanFilter scanFilter = new ScanFilter.Builder()
                 .setServiceData(BluetoothUuid.HEARING_AID, new byte[]{0}).build();
 
         mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, List.of(scanFilter));
 
-        assertThat(mCachedDevice1.isHearingAidDevice()).isTrue();
+        verify(mCachedDevice1).setHearingAidInfo(new HearingAidInfo.Builder().build());
     }
 
     /**
-     * Test initHearingAidDeviceIfNeeded, an invalid HiSyncId and random scan filter, not to set
-     * hearing aid info on the device.
+     * Test initHearingAidDeviceIfNeeded
+     *
+     * Conditions:
+     *      1) Asha hearing aid
+     *      2) Invalid HiSyncId
+     *      3) Random scan filter
+     * Result:
+     *      Do not set hearing aid info to the device.
      */
     @Test
-    public void initHearingAidDeviceIfNeeded_randomScanFilter_setHearingAidInfo() {
-        when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(
-                BluetoothHearingAid.HI_SYNC_ID_INVALID);
+    public void initHearingAidDeviceIfNeeded_asha_randomScanFilter_notToSetHearingAidInfo() {
+        when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+        when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HI_SYNC_ID_INVALID);
         final ScanFilter scanFilter = new ScanFilter.Builder().build();
 
         mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, List.of(scanFilter));
 
-        assertThat(mCachedDevice1.isHearingAidDevice()).isFalse();
+        verify(mCachedDevice1, never()).setHearingAidInfo(any(HearingAidInfo.class));
+    }
+
+    /**
+     * Test initHearingAidDeviceIfNeeded
+     *
+     * Conditions:
+     *      1) LeAudio hearing aid
+     *      2) Valid audio location and device type
+     * Result:
+     *      Set hearing aid info to the device.
+     */
+    @Test
+    public void initHearingAidDeviceIfNeeded_leAudio_validInfo_setHearingAidInfo() {
+        when(mCachedDevice1.getProfiles()).thenReturn(List.of(mLeAudioProfile, mHapClientProfile));
+        when(mLeAudioProfile.getAudioLocation(mDevice1)).thenReturn(AUDIO_LOCATION_FRONT_LEFT);
+        when(mHapClientProfile.getHearingAidType(mDevice1)).thenReturn(TYPE_BINAURAL);
+
+        mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, null);
+
+        verify(mCachedDevice1).setHearingAidInfo(any(HearingAidInfo.class));
+        assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_LEFT);
+        assertThat(mCachedDevice1.getDeviceMode()).isEqualTo(
+                HearingAidInfo.DeviceMode.MODE_BINAURAL);
+    }
+
+    /**
+     * Test initHearingAidDeviceIfNeeded
+     *
+     * Conditions:
+     *      1) LeAudio hearing aid
+     *      2) Invalid audio location and device type
+     * Result:
+     *      Do not set hearing aid info to the device.
+     */
+    @Test
+    public void initHearingAidDeviceIfNeeded_leAudio_invalidInfo_notToSetHearingAidInfo() {
+        when(mCachedDevice1.getProfiles()).thenReturn(List.of(mLeAudioProfile, mHapClientProfile));
+        when(mLeAudioProfile.getAudioLocation(mDevice1)).thenReturn(AUDIO_LOCATION_INVALID);
+        when(mHapClientProfile.getHearingAidType(mDevice1)).thenReturn(TYPE_INVALID);
+
+        mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, null);
+
+        verify(mCachedDevice1, never()).setHearingAidInfo(any(HearingAidInfo.class));
     }
 
     /**
@@ -234,13 +312,20 @@
     }
 
     /**
-     * Test updateHearingAidsDevices, to link two devices with the same HiSyncId.
-     * When first paired devices is connected and second paired device is disconnected, first
-     * paired device would be set as main device and second device will be removed from
-     * CachedDevices list.
+     * Test updateHearingAidsDevices
+     *
+     * Conditions:
+     *      1) Two ASHA hearing aids with the same HiSyncId
+     *      2) First paired devices is connected
+     *      3) Second paired device is disconnected
+     * Result:
+     *      First paired device would be set as main device and second paired device will be set
+     *      as sub device and removed from CachedDevices list.
      */
     @Test
-    public void updateHearingAidsDevices_firstPairedDevicesConnected_verifySubDevice() {
+    public void updateHearingAidsDevices_asha_firstPairedDevicesConnected_verifySubDevice() {
+        when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+        when(mCachedDevice2.getProfiles()).thenReturn(List.of(mHearingAidProfile));
         when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
         when(mHearingAidProfile.getHiSyncId(mDevice2)).thenReturn(HISYNCID1);
         when(mCachedDevice1.isConnected()).thenReturn(true);
@@ -257,13 +342,20 @@
     }
 
     /**
-     * Test updateHearingAidsDevices, to link two devices with the same HiSyncId.
-     * When second paired devices is connected and first paired device is disconnected, second
-     * paired device would be set as main device and first device will be removed from
-     * CachedDevices list.
+     * Test updateHearingAidsDevices
+     *
+     * Conditions:
+     *      1) Two ASHA hearing aids with the same HiSyncId
+     *      2) First paired devices is disconnected
+     *      3) Second paired device is connected
+     * Result:
+     *      Second paired device would be set as main device and first paired device will be set
+     *      as sub device and removed from CachedDevices list.
      */
     @Test
-    public void updateHearingAidsDevices_secondPairedDeviceConnected_verifySubDevice() {
+    public void updateHearingAidsDevices_asha_secondPairedDeviceConnected_verifySubDevice() {
+        when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+        when(mCachedDevice2.getProfiles()).thenReturn(List.of(mHearingAidProfile));
         when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
         when(mHearingAidProfile.getHiSyncId(mDevice2)).thenReturn(HISYNCID1);
         when(mCachedDevice1.isConnected()).thenReturn(false);
@@ -280,12 +372,20 @@
     }
 
     /**
-     * Test updateHearingAidsDevices, to link two devices with the same HiSyncId.
-     * When both devices are connected, to build up main and sub relationship and to remove sub
-     * device from CachedDevices list.
+     * Test updateHearingAidsDevices
+     *
+     * Conditions:
+     *      1) Two ASHA hearing aids with the same HiSyncId
+     *      2) First paired devices is connected
+     *      3) Second paired device is connected
+     * Result:
+     *      First paired device would be set as main device and second paired device will be set
+     *      as sub device and removed from CachedDevices list.
      */
     @Test
-    public void updateHearingAidsDevices_BothConnected_verifySubDevice() {
+    public void updateHearingAidsDevices_asha_bothConnected_verifySubDevice() {
+        when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+        when(mCachedDevice2.getProfiles()).thenReturn(List.of(mHearingAidProfile));
         when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
         when(mHearingAidProfile.getHiSyncId(mDevice2)).thenReturn(HISYNCID1);
         when(mCachedDevice1.isConnected()).thenReturn(true);
@@ -302,46 +402,64 @@
     }
 
     /**
-     * Test updateHearingAidsDevices, dispatch callback
+     * Test updateHearingAidsDevices
+     *
+     * Conditions:
+     *      1) Two ASHA hearing aids with the same HiSyncId
+     * Result:
+     *      Dispatch device removed callback
      */
     @Test
-    public void updateHearingAidsDevices_dispatchDeviceRemovedCallback() {
+    public void updateHearingAidsDevices_asha_dispatchDeviceRemovedCallback() {
+        when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+        when(mCachedDevice2.getProfiles()).thenReturn(List.of(mHearingAidProfile));
         when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
         when(mHearingAidProfile.getHiSyncId(mDevice2)).thenReturn(HISYNCID1);
         mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
         mCachedDeviceManager.mCachedDevices.add(mCachedDevice2);
+
         mHearingAidDeviceManager.updateHearingAidsDevices();
 
         verify(mBluetoothEventManager).dispatchDeviceRemoved(mCachedDevice1);
     }
 
     /**
-     * Test updateHearingAidsDevices, do nothing when HiSyncId is invalid
+     * Test updateHearingAidsDevices
+     *
+     * Conditions:
+     *      1) Two ASHA hearing aids with invalid HiSyncId
+     * Result:
+     *      Do nothing
      */
     @Test
-    public void updateHearingAidsDevices_invalidHiSyncId_doNothing() {
-        when(mHearingAidProfile.getHiSyncId(mDevice1)).
-                thenReturn(BluetoothHearingAid.HI_SYNC_ID_INVALID);
-        when(mHearingAidProfile.getHiSyncId(mDevice2)).
-                thenReturn(BluetoothHearingAid.HI_SYNC_ID_INVALID);
+    public void updateHearingAidsDevices_asha_invalidHiSyncId_doNothing() {
+        when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+        when(mCachedDevice2.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+        when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HI_SYNC_ID_INVALID);
+        when(mHearingAidProfile.getHiSyncId(mDevice2)).thenReturn(HI_SYNC_ID_INVALID);
         mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
         mCachedDeviceManager.mCachedDevices.add(mCachedDevice2);
+
         mHearingAidDeviceManager.updateHearingAidsDevices();
 
         verify(mHearingAidDeviceManager, never()).onHiSyncIdChanged(anyLong());
     }
 
     /**
-     * Test updateHearingAidsDevices, set HearingAid's information, including HiSyncId, deviceSide,
-     * deviceMode.
+     * Test updateHearingAidsDevices
+     *
+     * Conditions:
+     *      1) ASHA hearing aids
+     *      2) Valid HiSync Id
+     * Result:
+     *      Set hearing aid info to the device.
      */
     @Test
-    public void updateHearingAidsDevices_validHiSyncId_setHearingAidInfos() {
+    public void updateHearingAidsDevices_asha_validHiSyncId_setHearingAidInfo() {
+        when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
         when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
-        when(mHearingAidProfile.getDeviceMode(mDevice1)).thenReturn(
-                HearingAidProfile.DeviceMode.MODE_BINAURAL);
-        when(mHearingAidProfile.getDeviceSide(mDevice1)).thenReturn(
-                HearingAidProfile.DeviceSide.SIDE_RIGHT);
+        when(mHearingAidProfile.getDeviceMode(mDevice1)).thenReturn(MODE_BINAURAL);
+        when(mHearingAidProfile.getDeviceSide(mDevice1)).thenReturn(SIDE_RIGHT);
         mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
 
         mHearingAidDeviceManager.updateHearingAidsDevices();
@@ -355,6 +473,51 @@
     }
 
     /**
+     * Test updateHearingAidsDevices
+     *
+     * Conditions:
+     *      1) LeAudio hearing aid
+     *      2) Valid audio location and device type
+     * Result:
+     *      Set hearing aid info to the device.
+     */
+    @Test
+    public void updateHearingAidsDevices_leAudio_validInfo_setHearingAidInfo() {
+        when(mCachedDevice1.getProfiles()).thenReturn(List.of(mLeAudioProfile, mHapClientProfile));
+        when(mLeAudioProfile.getAudioLocation(mDevice1)).thenReturn(AUDIO_LOCATION_FRONT_LEFT);
+        when(mHapClientProfile.getHearingAidType(mDevice1)).thenReturn(TYPE_BINAURAL);
+        mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
+
+        mHearingAidDeviceManager.updateHearingAidsDevices();
+
+        verify(mCachedDevice1).setHearingAidInfo(any(HearingAidInfo.class));
+        assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_LEFT);
+        assertThat(mCachedDevice1.getDeviceMode()).isEqualTo(
+                HearingAidInfo.DeviceMode.MODE_BINAURAL);
+    }
+
+    /**
+     * Test updateHearingAidsDevices
+     *
+     * Conditions:
+     *      1) LeAudio hearing aid
+     *      2) Invalid audio location and device type
+     * Result:
+     *      Do not set hearing aid info to the device.
+     */
+    @Test
+    public void updateHearingAidsDevices_leAudio_invalidInfo_notToSetHearingAidInfo() {
+        when(mCachedDevice1.getProfiles()).thenReturn(List.of(mLeAudioProfile, mHapClientProfile));
+        when(mLeAudioProfile.getAudioLocation(mDevice1)).thenReturn(AUDIO_LOCATION_INVALID);
+        when(mHapClientProfile.getHearingAidType(mDevice1)).thenReturn(TYPE_INVALID);
+        mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
+
+        mHearingAidDeviceManager.updateHearingAidsDevices();
+
+        verify(mCachedDevice1, never()).setHearingAidInfo(any(HearingAidInfo.class));
+    }
+
+    /**
      * Test onProfileConnectionStateChangedIfProcessed.
      * When first hearing aid device is connected, to process it same as other generic devices.
      * No need to process it.
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 89a8dd9..17d9f1b 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -335,4 +335,7 @@
     <!-- Default for Settings.BATTERY_CHARGING_STATE_ENFORCE_LEVEL.
         -1 means system internal default value is used. -->
     <integer name="def_battery_charging_state_enforce_level">-1</integer>
+
+    <!-- Value to use as default scale for fonts -->
+    <item name="def_device_font_scale" format="float" type="dimen">1.0</item>
 </resources>
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 8ae50eb..8ad5f24 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -74,6 +74,7 @@
         Settings.Secure.TTS_DEFAULT_LOCALE,
         Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
         Settings.Secure.ACCESSIBILITY_BOUNCE_KEYS,
+        Settings.Secure.ACCESSIBILITY_SLOW_KEYS,
         Settings.Secure.ACCESSIBILITY_STICKY_KEYS,
         Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,            // moved to global
         Settings.Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY,               // moved to global
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index e7d7bb0..38ec931 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -48,6 +48,7 @@
                 Settings.System.WIFI_STATIC_DNS2,
                 Settings.System.BLUETOOTH_DISCOVERABILITY,
                 Settings.System.BLUETOOTH_DISCOVERABILITY_TIMEOUT,
+                Settings.System.DEFAULT_DEVICE_FONT_SCALE,
                 Settings.System.FONT_SCALE,
                 Settings.System.DIM_SCREEN,
                 Settings.System.SCREEN_OFF_TIMEOUT,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 285c8c9..d854df38 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -120,6 +120,7 @@
         VALIDATORS.put(Secure.TTS_DEFAULT_LOCALE, TTS_LIST_VALIDATOR);
         VALIDATORS.put(Secure.SHOW_IME_WITH_HARD_KEYBOARD, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ACCESSIBILITY_BOUNCE_KEYS, ANY_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.ACCESSIBILITY_SLOW_KEYS, ANY_INTEGER_VALIDATOR);
         VALIDATORS.put(Secure.ACCESSIBILITY_STICKY_KEYS, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, NON_NEGATIVE_INTEGER_VALIDATOR);
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java
index a8a659e..677c81a 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java
@@ -45,6 +45,9 @@
         }
     };
 
+    public static final Validator FONT_SCALE_VALIDATOR = new InclusiveFloatRangeValidator(0.25f,
+            5.0f);
+
     public static final Validator NON_NEGATIVE_INTEGER_VALIDATOR = new Validator() {
         @Override
         public boolean validate(@Nullable String value) {
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 572303a..98941c7 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -20,6 +20,7 @@
 import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.COMPONENT_NAME_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.FONT_SCALE_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.LENIENT_IP_ADDRESS_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.NON_NEGATIVE_FLOAT_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR;
@@ -31,7 +32,6 @@
 import android.content.ComponentName;
 import android.hardware.display.ColorDisplayManager;
 import android.os.BatteryManager;
-import android.provider.Settings.Global;
 import android.provider.Settings.System;
 import android.util.ArrayMap;
 
@@ -93,7 +93,8 @@
                         return value == null || value.length() < MAX_LENGTH;
                     }
                 });
-        VALIDATORS.put(System.FONT_SCALE, new InclusiveFloatRangeValidator(0.25f, 5.0f));
+        VALIDATORS.put(System.DEFAULT_DEVICE_FONT_SCALE, FONT_SCALE_VALIDATOR);
+        VALIDATORS.put(System.FONT_SCALE, FONT_SCALE_VALIDATOR);
         VALIDATORS.put(System.DIM_SCREEN, BOOLEAN_VALIDATOR);
         VALIDATORS.put(
                 System.DISPLAY_COLOR_MODE,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 3a46f4e..febce97 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3812,7 +3812,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 224;
+            private static final int SETTINGS_VERSION = 225;
 
             private final int mUserId;
 
@@ -6004,6 +6004,13 @@
                     currentVersion = 224;
                 }
 
+                // Version 224: Update the default font scale depending on the
+                //              R.dimen.def_device_font_scale configuration property.
+                if (currentVersion == 224) {
+                    handleDefaultFontScale(getSystemSettingsLocked(userId));
+                    currentVersion = 225;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 if (currentVersion != newVersion) {
@@ -6021,6 +6028,32 @@
                 return currentVersion;
             }
 
+            @SuppressWarnings("GuardedBy")
+            @GuardedBy("mLock")
+            private void handleDefaultFontScale(@NonNull SettingsState systemSettings) {
+                final float defaultFontScale = getContext().getResources()
+                        .getFloat(R.dimen.def_device_font_scale);
+                // Persist the value for future use (e.g. Reset Settings option)
+                systemSettings.insertSettingLocked(
+                        Settings.System.DEFAULT_DEVICE_FONT_SCALE,
+                        String.valueOf(defaultFontScale),
+                        /* tag= */ null,
+                        /* makeDefault= */ false,
+                        SettingsState.SYSTEM_PACKAGE_NAME);
+                // We verify if there is a pre existing value for font_scale.
+                final Setting existingFontScale = systemSettings.getSettingLocked(
+                        Settings.System.FONT_SCALE);
+                if (existingFontScale == null || existingFontScale.isNull()) {
+                    // Set the default value only if it didn't exist before
+                    systemSettings.insertSettingLocked(
+                            Settings.System.FONT_SCALE,
+                            String.valueOf(defaultFontScale),
+                            /* tag= */ null,
+                            /* makeDefault= */ false,
+                            SettingsState.SYSTEM_PACKAGE_NAME);
+                }
+            }
+
             @GuardedBy("mLock")
             private void initGlobalSettingsDefaultValLocked(String key, boolean val) {
                 initGlobalSettingsDefaultValLocked(key, val ? "1" : "0");
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index d384542..cdb4aea 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -561,7 +561,7 @@
     <uses-permission android:name="android.permission.TEST_BIOMETRIC" />
 
     <!-- Permission required for CTS test - android.server.biometrics -->
-    <uses-permission android:name="android.permission.MANAGE_BIOMETRIC_DIALOG" />
+    <uses-permission android:name="android.permission.SET_BIOMETRIC_DIALOG_LOGO" />
 
     <!-- Permission required for CTS test - android.server.biometrics -->
     <uses-permission android:name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION" />
@@ -902,6 +902,9 @@
     <!-- Permission required for BinaryTransparencyService shell API and host test -->
     <uses-permission android:name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES" />
 
+    <!-- Permissions required for CTS test - CtsPermissionUiTestCases -->
+    <uses-permission android:name="android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES" />
+
     <application
         android:label="@string/app_label"
         android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index 7ba889b..866aa89 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -17,6 +17,13 @@
 }
 
 flag {
+    name: "floating_menu_drag_to_edit"
+    namespace: "accessibility"
+    description: "adds a second drag button to allow the user edit the shortcut."
+    bug: "297583708"
+}
+
+flag {
     name: "floating_menu_ime_displacement_animation"
     namespace: "accessibility"
     description: "Adds an animation for when the FAB is displaced by an IME becoming visible."
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 7eca04a..a2530d5 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -366,8 +366,8 @@
 }
 
 flag {
-   name: "enable_notif_linearlayout_optimized"
-   namespace: "systemui"
-   description: "Enables notification specific LinearLayout optimization"
-   bug: "316110233"
+    name: "keyguard_wm_state_refactor"
+    namespace: "systemui"
+    description: "Enables refactored logic for SysUI+WM unlock/occlusion code paths"
+    bug: "278086361"
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index a22fecf..4fdcf75 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -84,13 +84,14 @@
     SceneTransitionLayout(
         state = sceneTransitionLayoutState,
         modifier = modifier.fillMaxSize(),
-        edgeDetector = FixedSizeEdgeDetector(ContainerDimensions.EdgeSwipeSize),
+        swipeSourceDetector = FixedSizeEdgeDetector(ContainerDimensions.EdgeSwipeSize),
     ) {
         scene(
             TransitionSceneKey.Blank,
             userActions =
                 mapOf(
-                    Swipe(SwipeDirection.Left, fromEdge = Edge.Right) to TransitionSceneKey.Communal
+                    Swipe(SwipeDirection.Left, fromSource = Edge.Right) to
+                        TransitionSceneKey.Communal
                 )
         ) {
             // This scene shows nothing only allowing for transitions to the communal scene.
@@ -101,7 +102,7 @@
             TransitionSceneKey.Communal,
             userActions =
                 mapOf(
-                    Swipe(SwipeDirection.Right, fromEdge = Edge.Left) to TransitionSceneKey.Blank
+                    Swipe(SwipeDirection.Right, fromSource = Edge.Left) to TransitionSceneKey.Blank
                 ),
         ) {
             CommunalScene(viewModel, modifier = modifier)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 409f15b..761e74e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -97,11 +97,13 @@
 import androidx.compose.ui.viewinterop.AndroidView
 import androidx.compose.ui.window.Popup
 import androidx.core.view.setPadding
+import com.android.compose.modifiers.thenIf
 import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.CommunalContentSize
 import com.android.systemui.communal.ui.compose.Dimensions.CardOutlineWidth
 import com.android.systemui.communal.ui.compose.extensions.allowGestures
+import com.android.systemui.communal.ui.compose.extensions.detectLongPressGesture
 import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset
 import com.android.systemui.communal.ui.compose.extensions.observeTapsWithoutConsuming
 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
@@ -132,6 +134,8 @@
     val removeButtonEnabled by remember {
         derivedStateOf { selectedIndex.value != null || reorderingWidgets }
     }
+    val (isButtonToEditWidgetsShowing, setIsButtonToEditWidgetsShowing) =
+        remember { mutableStateOf(false) }
 
     val contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize)
     val contentOffset = beforeContentPadding(contentPadding).toOffset()
@@ -158,6 +162,11 @@
                             }
                         viewModel.setSelectedIndex(newIndex)
                     }
+                }
+                .thenIf(!viewModel.isEditMode) {
+                    Modifier.pointerInput(Unit) {
+                        detectLongPressGesture { offset -> setIsButtonToEditWidgetsShowing(true) }
+                    }
                 },
     ) {
         CommunalHubLazyGrid(
@@ -207,6 +216,16 @@
             PopupOnDismissCtaTile(viewModel::onHidePopupAfterDismissCta)
         }
 
+        if (isButtonToEditWidgetsShowing) {
+            ButtonToEditWidgets(
+                onClick = {
+                    setIsButtonToEditWidgetsShowing(false)
+                    viewModel.onOpenWidgetEditor()
+                },
+                onHide = { setIsButtonToEditWidgetsShowing(false) },
+            )
+        }
+
         // This spacer covers the edge of the LazyHorizontalGrid and prevents it from receiving
         // touches, so that the SceneTransitionLayout can intercept the touches and allow an edge
         // swipe back to the blank scene.
@@ -414,6 +433,34 @@
 }
 
 @Composable
+private fun ButtonToEditWidgets(
+    onClick: () -> Unit,
+    onHide: () -> Unit,
+) {
+    Popup(alignment = Alignment.TopCenter, offset = IntOffset(0, 40), onDismissRequest = onHide) {
+        val colors = LocalAndroidColorScheme.current
+        Button(
+            modifier =
+                Modifier.height(56.dp).background(colors.secondary, RoundedCornerShape(50.dp)),
+            onClick = onClick,
+        ) {
+            Icon(
+                imageVector = Icons.Outlined.Widgets,
+                contentDescription = stringResource(R.string.button_to_configure_widgets_text),
+                tint = colors.onSecondary,
+                modifier = Modifier.size(20.dp)
+            )
+            Spacer(modifier = Modifier.size(8.dp))
+            Text(
+                text = stringResource(R.string.button_to_configure_widgets_text),
+                style = MaterialTheme.typography.titleSmall,
+                color = colors.onSecondary,
+            )
+        }
+    }
+}
+
+@Composable
 private fun PopupOnDismissCtaTile(onHidePopupAfterDismissCta: () -> Unit) {
     Popup(
         alignment = Alignment.TopCenter,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
index 1407494..bc1e429 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
@@ -20,9 +20,13 @@
 import androidx.compose.foundation.gestures.awaitFirstDown
 import androidx.compose.foundation.gestures.waitForUpOrCancellation
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.AwaitPointerEventScope
 import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.PointerEventTimeoutCancellationException
 import androidx.compose.ui.input.pointer.PointerInputChange
 import androidx.compose.ui.input.pointer.PointerInputScope
+import androidx.compose.ui.util.fastAny
+import androidx.compose.ui.util.fastForEach
 import kotlinx.coroutines.coroutineScope
 
 /**
@@ -44,6 +48,41 @@
     }
 }
 
+/**
+ * Detect long press gesture and calls onLongPress when detected. The callback parameter receives an
+ * Offset representing the position relative to the containing element.
+ */
+suspend fun PointerInputScope.detectLongPressGesture(
+    pass: PointerEventPass = PointerEventPass.Initial,
+    onLongPress: ((Offset) -> Unit),
+) = coroutineScope {
+    awaitEachGesture {
+        val down = awaitFirstDown(pass = pass)
+        val longPressTimeout = viewConfiguration.longPressTimeoutMillis
+        // wait for first tap up or long press
+        try {
+            withTimeout(longPressTimeout) { waitForUpOrCancellation(pass = pass) }
+        } catch (_: PointerEventTimeoutCancellationException) {
+            // withTimeout throws exception if timeout has passed before block completes
+            onLongPress.invoke(down.position)
+            consumeUntilUp(pass)
+        }
+    }
+}
+
+/**
+ * Consumes all pointer events until nothing is pressed and then returns. This method assumes that
+ * something is currently pressed.
+ */
+private suspend fun AwaitPointerEventScope.consumeUntilUp(
+    pass: PointerEventPass = PointerEventPass.Initial
+) {
+    do {
+        val event = awaitPointerEvent(pass = pass)
+        event.changes.fastForEach { it.consume() }
+    } while (event.changes.fastAny { it.pressed })
+}
+
 /** Consume all gestures on the initial pass so that child elements do not receive them. */
 suspend fun PointerInputScope.consumeAllGestures() = coroutineScope {
     awaitEachGesture {
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 56d6879..bf02d8a 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
@@ -173,7 +173,8 @@
                 val belowLockIconPlaceable =
                     belowLockIconMeasurable.measure(
                         noMinConstraints.copy(
-                            maxHeight = constraints.maxHeight - lockIconBounds.bottom
+                            maxHeight =
+                                (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0)
                         )
                     )
                 val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints)
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
index fdf1166..616a7b4 100644
--- 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
@@ -16,19 +16,42 @@
 
 package com.android.systemui.keyguard.ui.composable.blueprint
 
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.material3.Text
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+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.graphics.Color
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.platform.LocalContext
+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.domain.interactor.KeyguardClockInteractor
 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.ClockSection
+import com.android.systemui.keyguard.ui.composable.section.LockSection
+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.SmartSpaceSection
+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
 
 /**
@@ -39,22 +62,174 @@
 @Inject
 constructor(
     private val viewModel: LockscreenContentViewModel,
+    private val statusBarSection: StatusBarSection,
+    private val clockSection: ClockSection,
+    private val smartSpaceSection: SmartSpaceSection,
+    private val notificationSection: NotificationSection,
+    private val lockSection: LockSection,
+    private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
+    private val bottomAreaSection: BottomAreaSection,
+    private val settingsMenuSection: SettingsMenuSection,
+    private val clockInteractor: KeyguardClockInteractor,
+    private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
 ) : LockscreenSceneBlueprint {
 
     override val id: String = "split-shade"
 
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
+        val isUdfpsVisible = viewModel.isUdfpsVisible
+        val burnIn = rememberBurnIn(clockInteractor)
+        val resources = LocalContext.current.resources
+
         LockscreenLongPress(
             viewModel = viewModel.longPress,
             modifier = modifier,
-        ) { _ ->
-            Box(modifier.background(Color.Black)) {
-                Text(
-                    text = "TODO(b/316211368): split shade blueprint",
-                    color = Color.White,
-                    modifier = Modifier.align(Alignment.Center),
-                )
+        ) { 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(smartSpaceSection) {
+                                    SmartSpace(
+                                        burnInParams = burnIn.parameters,
+                                        onTopChanged = burnIn.onSmartspaceTopChanged,
+                                        modifier =
+                                            Modifier.fillMaxWidth()
+                                                .padding(
+                                                    top = {
+                                                        viewModel.getSmartSpacePaddingTop(resources)
+                                                    }
+                                                ),
+                                    )
+                                }
+
+                                Spacer(modifier = Modifier.weight(weight = 1f))
+                                with(clockSection) { LargeClock() }
+                                Spacer(modifier = Modifier.weight(weight = 1f))
+                            }
+                            with(notificationSection) {
+                                val splitShadeTopMargin: Dp =
+                                    if (Flags.centralizedStatusBarDimensRefactor()) {
+                                        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,
+                    )
+                }
             }
         }
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
index f40b871..8f21879 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
@@ -16,7 +16,8 @@
 
 package com.android.systemui.keyguard.ui.composable.section
 
-import androidx.compose.foundation.layout.fillMaxWidth
+import android.view.ViewGroup
+import android.widget.FrameLayout
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
@@ -75,7 +76,13 @@
         ) {
             content {
                 AndroidView(
-                    factory = { checkNotNull(currentClock).smallClock.view },
+                    factory = { context ->
+                        FrameLayout(context).apply {
+                            val newClockView = checkNotNull(currentClock).smallClock.view
+                            (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+                            addView(newClockView)
+                        }
+                    },
                     modifier =
                         Modifier.padding(
                                 horizontal =
@@ -83,6 +90,12 @@
                             )
                             .padding(top = { viewModel.getSmallClockTopMargin(view.context) })
                             .onTopPlacementChanged(onTopChanged),
+                    update = {
+                        val newClockView = checkNotNull(currentClock).smallClock.view
+                        it.removeAllViews()
+                        (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+                        it.addView(newClockView)
+                    },
                 )
             }
         }
@@ -116,8 +129,19 @@
         ) {
             content {
                 AndroidView(
-                    factory = { checkNotNull(currentClock).largeClock.view },
-                    modifier = Modifier.fillMaxWidth()
+                    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)
+                    },
                 )
             }
         }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
index 2a6bea7..be6f022 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
@@ -33,6 +33,7 @@
 import com.android.keyguard.LockIconViewController
 import com.android.systemui.Flags.keyguardBottomAreaRefactor
 import com.android.systemui.biometrics.AuthController
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
@@ -47,10 +48,12 @@
 import com.android.systemui.statusbar.VibratorHelper
 import dagger.Lazy
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
 
 class LockSection
 @Inject
 constructor(
+    @Application private val applicationScope: CoroutineScope,
     private val windowManager: WindowManager,
     private val authController: AuthController,
     private val featureFlags: FeatureFlagsClassic,
@@ -76,6 +79,7 @@
                         DeviceEntryIconView(context, null).apply {
                             id = R.id.device_entry_icon_view
                             DeviceEntryIconViewBinder.bind(
+                                applicationScope,
                                 this,
                                 deviceEntryIconViewModel.get(),
                                 deviceEntryForegroundViewModel.get(),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index c35202c..9f9e1f5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -183,7 +183,7 @@
         is UserAction.Swipe ->
             Swipe(
                 pointerCount = pointerCount,
-                fromEdge =
+                fromSource =
                     when (this.fromEdge) {
                         null -> null
                         Edge.LEFT -> SceneTransitionEdge.Left
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt
index 82d4239..b0dc3a1 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt
@@ -23,24 +23,19 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
 
-interface EdgeDetector {
-    /**
-     * Return the [Edge] associated to [position] inside a layout of size [layoutSize], given
-     * [density] and [orientation].
-     */
-    fun edge(
-        layoutSize: IntSize,
-        position: IntOffset,
-        density: Density,
-        orientation: Orientation,
-    ): Edge?
+/** The edge of a [SceneTransitionLayout]. */
+enum class Edge : SwipeSource {
+    Left,
+    Right,
+    Top,
+    Bottom,
 }
 
 val DefaultEdgeDetector = FixedSizeEdgeDetector(40.dp)
 
-/** An [EdgeDetector] that detects edges assuming a fixed edge size of [size]. */
-class FixedSizeEdgeDetector(val size: Dp) : EdgeDetector {
-    override fun edge(
+/** An [SwipeSourceDetector] that detects edges assuming a fixed edge size of [size]. */
+class FixedSizeEdgeDetector(val size: Dp) : SwipeSourceDetector {
+    override fun source(
         layoutSize: IntSize,
         position: IntOffset,
         density: Density,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
index 90f46bd..9d4b69c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
@@ -44,7 +44,7 @@
 class SceneKey(
     name: String,
     identity: Any = Object(),
-) : Key(name, identity) {
+) : Key(name, identity), UserActionResult {
     @VisibleForTesting
     // TODO(b/240432457): Make internal once PlatformComposeSceneTransitionLayoutTestsUtils can
     // access internal members.
@@ -53,6 +53,10 @@
     /** The unique [ElementKey] identifying this scene's root element. */
     val rootElementKey = ElementKey(name, identity)
 
+    // Implementation of [UserActionResult].
+    override val toScene: SceneKey = this
+    override val distance: UserActionDistance? = null
+
     override fun toString(): String {
         return "SceneKey(debugName=$debugName)"
     }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index 3873878..8552aaf 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -64,8 +64,8 @@
 @Stable
 internal fun Modifier.multiPointerDraggable(
     orientation: Orientation,
-    enabled: Boolean,
-    startDragImmediately: Boolean,
+    enabled: () -> Boolean,
+    startDragImmediately: () -> Boolean,
     onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
     onDragDelta: (delta: Float) -> Unit,
     onDragStopped: (velocity: Float) -> Unit,
@@ -83,8 +83,8 @@
 
 private data class MultiPointerDraggableElement(
     private val orientation: Orientation,
-    private val enabled: Boolean,
-    private val startDragImmediately: Boolean,
+    private val enabled: () -> Boolean,
+    private val startDragImmediately: () -> Boolean,
     private val onDragStarted:
         (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
     private val onDragDelta: (Float) -> Unit,
@@ -110,10 +110,10 @@
     }
 }
 
-private class MultiPointerDraggableNode(
+internal class MultiPointerDraggableNode(
     orientation: Orientation,
-    enabled: Boolean,
-    var startDragImmediately: Boolean,
+    enabled: () -> Boolean,
+    var startDragImmediately: () -> Boolean,
     var onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
     var onDragDelta: (Float) -> Unit,
     var onDragStopped: (velocity: Float) -> Unit,
@@ -122,7 +122,7 @@
     private val delegate = delegate(SuspendingPointerInputModifierNode(pointerInputHandler))
     private val velocityTracker = VelocityTracker()
 
-    var enabled: Boolean = enabled
+    var enabled: () -> Boolean = enabled
         set(value) {
             // Reset the pointer input whenever enabled changed.
             if (value != field) {
@@ -133,7 +133,7 @@
 
     var orientation: Orientation = orientation
         set(value) {
-            // Reset the pointer input whenever enabled orientation.
+            // Reset the pointer input whenever orientation changed.
             if (value != field) {
                 field = value
                 delegate.resetPointerInputHandler()
@@ -149,7 +149,7 @@
     ) = delegate.onPointerEvent(pointerEvent, pass, bounds)
 
     private suspend fun PointerInputScope.pointerInput() {
-        if (!enabled) {
+        if (!enabled()) {
             return
         }
 
@@ -163,8 +163,7 @@
         val onDragEnd: () -> Unit = {
             val maxFlingVelocity =
                 currentValueOf(LocalViewConfiguration).maximumFlingVelocity.let { max ->
-                    val maxF = max.toFloat()
-                    Velocity(maxF, maxF)
+                    Velocity(max, max)
                 }
 
             val velocity = velocityTracker.calculateVelocity(maxFlingVelocity)
@@ -183,7 +182,7 @@
 
         detectDragGestures(
             orientation = orientation,
-            startDragImmediately = { startDragImmediately },
+            startDragImmediately = startDragImmediately,
             onDragStart = onDragStart,
             onDragEnd = onDragEnd,
             onDragCancel = onDragCancel,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index f67df54..af51cee 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -38,7 +38,7 @@
     val key: SceneKey,
     layoutImpl: SceneTransitionLayoutImpl,
     content: @Composable SceneScope.() -> Unit,
-    actions: Map<UserAction, SceneKey>,
+    actions: Map<UserAction, UserActionResult>,
     zIndex: Float,
 ) {
     internal val scope = SceneScopeImpl(layoutImpl, this)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
index ff05478..aed04f6 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
@@ -28,6 +28,8 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.round
 import com.android.compose.nestedscroll.PriorityNestedScrollConnection
@@ -75,23 +77,25 @@
 
     internal var currentSource: Any? = null
 
-    /** The [UserAction]s associated to the current swipe. */
-    private var actionUpOrLeft: UserAction? = null
-    private var actionDownOrRight: UserAction? = null
-    private var actionUpOrLeftNoEdge: UserAction? = null
-    private var actionDownOrRightNoEdge: UserAction? = null
-    private var upOrLeftScene: SceneKey? = null
-    private var downOrRightScene: SceneKey? = null
+    /** The [Swipes] associated to the current gesture. */
+    private var swipes: Swipes? = null
+
+    /** The [UserActionResult] associated to up and down swipes. */
+    private var upOrLeftResult: UserActionResult? = null
+    private var downOrRightResult: UserActionResult? = null
 
     internal fun onDragStarted(pointersDown: Int, startedPosition: Offset?, overSlop: Float) {
         if (isDrivingTransition) {
             // This [transition] was already driving the animation: simply take over it.
             // Stop animating and start from where the current offset.
             swipeTransition.cancelOffsetAnimation()
-            updateTargetScenes(swipeTransition._fromScene)
+            updateSwipesResults(swipeTransition._fromScene)
             return
         }
 
+        check(overSlop != 0f) {
+            "onDragStarted() called while isDrivingTransition=false overSlop=0f"
+        }
         val transitionState = layoutState.transitionState
         if (transitionState is TransitionState.Transition) {
             // TODO(b/290184746): Better handle interruptions here if state != idle.
@@ -104,18 +108,25 @@
         }
 
         val fromScene = layoutImpl.scene(transitionState.currentScene)
-        setCurrentActions(fromScene, startedPosition, pointersDown)
+        updateSwipes(fromScene, startedPosition, pointersDown)
 
         val (targetScene, distance) =
-            findTargetSceneAndDistance(fromScene, overSlop, updateScenes = true) ?: return
-
+            findTargetSceneAndDistance(fromScene, overSlop, updateSwipesResults = true) ?: return
         updateTransition(SwipeTransition(fromScene, targetScene, distance), force = true)
     }
 
-    private fun setCurrentActions(fromScene: Scene, startedPosition: Offset?, pointersDown: Int) {
-        val fromEdge =
+    private fun updateSwipes(fromScene: Scene, startedPosition: Offset?, pointersDown: Int) {
+        this.swipes = computeSwipes(fromScene, startedPosition, pointersDown)
+    }
+
+    private fun computeSwipes(
+        fromScene: Scene,
+        startedPosition: Offset?,
+        pointersDown: Int
+    ): Swipes {
+        val fromSource =
             startedPosition?.let { position ->
-                layoutImpl.edgeDetector.edge(
+                layoutImpl.swipeSourceDetector.source(
                     fromScene.targetSize,
                     position.round(),
                     layoutImpl.density,
@@ -131,7 +142,7 @@
                         Orientation.Vertical -> SwipeDirection.Up
                     },
                 pointerCount = pointersDown,
-                fromEdge = fromEdge,
+                fromSource = fromSource,
             )
 
         val downOrRight =
@@ -142,33 +153,31 @@
                         Orientation.Vertical -> SwipeDirection.Down
                     },
                 pointerCount = pointersDown,
-                fromEdge = fromEdge,
+                fromSource = fromSource,
             )
 
-        if (fromEdge == null) {
-            actionUpOrLeft = null
-            actionDownOrRight = null
-            actionUpOrLeftNoEdge = upOrLeft
-            actionDownOrRightNoEdge = downOrRight
+        return if (fromSource == null) {
+            Swipes(
+                upOrLeft = null,
+                downOrRight = null,
+                upOrLeftNoSource = upOrLeft,
+                downOrRightNoSource = downOrRight,
+            )
         } else {
-            actionUpOrLeft = upOrLeft
-            actionDownOrRight = downOrRight
-            actionUpOrLeftNoEdge = upOrLeft.copy(fromEdge = null)
-            actionDownOrRightNoEdge = downOrRight.copy(fromEdge = null)
+            Swipes(
+                upOrLeft = upOrLeft,
+                downOrRight = downOrRight,
+                upOrLeftNoSource = upOrLeft.copy(fromSource = null),
+                downOrRightNoSource = downOrRight.copy(fromSource = null),
+            )
         }
     }
 
-    /**
-     * Use the layout size in the swipe orientation for swipe distance.
-     *
-     * TODO(b/290184746): Also handle custom distances for transitions. With smaller distances, we
-     *   will also have to make sure that we correctly handle overscroll.
-     */
-    private fun Scene.getAbsoluteDistance(): Float {
-        return when (orientation) {
-            Orientation.Horizontal -> targetSize.width
-            Orientation.Vertical -> targetSize.height
-        }.toFloat()
+    private fun Scene.getAbsoluteDistance(distance: UserActionDistance?): Float {
+        val targetSize = this.targetSize
+        return with(distance ?: DefaultSwipeDistance) {
+            layoutImpl.density.absoluteDistance(targetSize, orientation)
+        }
     }
 
     internal fun onDrag(delta: Float) {
@@ -183,7 +192,7 @@
             findTargetSceneAndDistance(
                 fromScene,
                 swipeTransition.dragOffset,
-                updateScenes = isNewFromScene,
+                updateSwipesResults = isNewFromScene,
             )
                 ?: run {
                     onDragStopped(delta, true)
@@ -200,9 +209,31 @@
         }
     }
 
-    private fun updateTargetScenes(fromScene: Scene) {
-        upOrLeftScene = fromScene.upOrLeft()
-        downOrRightScene = fromScene.downOrRight()
+    private fun updateSwipesResults(fromScene: Scene) {
+        val (upOrLeftResult, downOrRightResult) =
+            swipesResults(
+                fromScene,
+                this.swipes ?: error("updateSwipes() should be called before updateSwipesResults()")
+            )
+
+        this.upOrLeftResult = upOrLeftResult
+        this.downOrRightResult = downOrRightResult
+    }
+
+    private fun swipesResults(
+        fromScene: Scene,
+        swipes: Swipes
+    ): Pair<UserActionResult?, UserActionResult?> {
+        val userActions = fromScene.userActions
+        fun sceneToSwipePair(swipe: Swipe?): UserActionResult? {
+            return userActions[swipe ?: return null]
+        }
+
+        val upOrLeftResult =
+            sceneToSwipePair(swipes.upOrLeft) ?: sceneToSwipePair(swipes.upOrLeftNoSource)
+        val downOrRightResult =
+            sceneToSwipePair(swipes.downOrRight) ?: sceneToSwipePair(swipes.downOrRightNoSource)
+        return Pair(upOrLeftResult, downOrRightResult)
     }
 
     /**
@@ -229,9 +260,9 @@
         // If the offset is past the distance then let's change fromScene so that the user can swipe
         // to the next screen or go back to the previous one.
         val offset = swipeTransition.dragOffset
-        return if (offset <= -absoluteDistance && upOrLeftScene == toScene.key) {
+        return if (offset <= -absoluteDistance && upOrLeftResult?.toScene == toScene.key) {
             Pair(toScene, absoluteDistance)
-        } else if (offset >= absoluteDistance && downOrRightScene == toScene.key) {
+        } else if (offset >= absoluteDistance && downOrRightResult?.toScene == toScene.key) {
             Pair(toScene, -absoluteDistance)
         } else {
             Pair(fromScene, 0f)
@@ -244,31 +275,41 @@
      * @param fromScene the scene from which we look for the target
      * @param directionOffset signed float that indicates the direction. Positive is down or right
      *   negative is up or left.
-     * @param updateScenes whether the target scenes should be updated to the current values held in
-     *   the Scenes map. Usually we don't want to update them while doing a drag, because this could
-     *   change the target scene (jump cutting) to a different scene, when some system state changed
-     *   the targets the background. However, an update is needed any time we calculate the targets
-     *   for a new fromScene.
+     * @param updateSwipesResults whether the target scenes should be updated to the current values
+     *   held in the Scenes map. Usually we don't want to update them while doing a drag, because
+     *   this could change the target scene (jump cutting) to a different scene, when some system
+     *   state changed the targets the background. However, an update is needed any time we
+     *   calculate the targets for a new fromScene.
      * @return null when there are no targets in either direction. If one direction is null and you
      *   drag into the null direction this function will return the opposite direction, assuming
      *   that the users intention is to start the drag into the other direction eventually. If
      *   [directionOffset] is 0f and both direction are available, it will default to
-     *   [upOrLeftScene].
+     *   [upOrLeftResult].
      */
     private inline fun findTargetSceneAndDistance(
         fromScene: Scene,
         directionOffset: Float,
-        updateScenes: Boolean,
+        updateSwipesResults: Boolean,
     ): Pair<Scene, Float>? {
-        if (updateScenes) updateTargetScenes(fromScene)
-        val absoluteDistance = fromScene.getAbsoluteDistance()
+        if (updateSwipesResults) updateSwipesResults(fromScene)
 
         // Compute the target scene depending on the current offset.
         return when {
-            upOrLeftScene == null && downOrRightScene == null -> null
-            (directionOffset < 0f && upOrLeftScene != null) || downOrRightScene == null ->
-                Pair(layoutImpl.scene(upOrLeftScene!!), -absoluteDistance)
-            else -> Pair(layoutImpl.scene(downOrRightScene!!), absoluteDistance)
+            upOrLeftResult == null && downOrRightResult == null -> null
+            (directionOffset < 0f && upOrLeftResult != null) || downOrRightResult == null ->
+                upOrLeftResult?.let { result ->
+                    Pair(
+                        layoutImpl.scene(result.toScene),
+                        -fromScene.getAbsoluteDistance(result.distance)
+                    )
+                }
+            else ->
+                downOrRightResult?.let { result ->
+                    Pair(
+                        layoutImpl.scene(result.toScene),
+                        fromScene.getAbsoluteDistance(result.distance)
+                    )
+                }
         }
     }
 
@@ -280,24 +321,25 @@
         fromScene: Scene,
         directionOffset: Float,
     ): Pair<Scene, Float>? {
-        val absoluteDistance = fromScene.getAbsoluteDistance()
         return when {
             directionOffset > 0f ->
-                upOrLeftScene?.let { Pair(layoutImpl.scene(it), -absoluteDistance) }
+                upOrLeftResult?.let { result ->
+                    Pair(
+                        layoutImpl.scene(result.toScene),
+                        -fromScene.getAbsoluteDistance(result.distance),
+                    )
+                }
             directionOffset < 0f ->
-                downOrRightScene?.let { Pair(layoutImpl.scene(it), absoluteDistance) }
+                downOrRightResult?.let { result ->
+                    Pair(
+                        layoutImpl.scene(result.toScene),
+                        fromScene.getAbsoluteDistance(result.distance),
+                    )
+                }
             else -> null
         }
     }
 
-    private fun Scene.upOrLeft(): SceneKey? {
-        return userActions[actionUpOrLeft] ?: userActions[actionUpOrLeftNoEdge]
-    }
-
-    private fun Scene.downOrRight(): SceneKey? {
-        return userActions[actionDownOrRight] ?: userActions[actionDownOrRightNoEdge]
-    }
-
     internal fun onDragStopped(velocity: Float, canChangeScene: Boolean) {
         // The state was changed since the drag started; don't do anything.
         if (!isDrivingTransition) {
@@ -515,6 +557,26 @@
     companion object {
         private const val TAG = "SceneGestureHandler"
     }
+
+    private object DefaultSwipeDistance : UserActionDistance {
+        override fun Density.absoluteDistance(
+            fromSceneSize: IntSize,
+            orientation: Orientation,
+        ): Float {
+            return when (orientation) {
+                Orientation.Horizontal -> fromSceneSize.width
+                Orientation.Vertical -> fromSceneSize.height
+            }.toFloat()
+        }
+    }
+
+    /** The [Swipe] associated to a given fromScene, startedPosition and pointersDown. */
+    private class Swipes(
+        val upOrLeft: Swipe?,
+        val downOrRight: Swipe?,
+        val upOrLeftNoSource: Swipe?,
+        val downOrRightNoSource: Swipe?,
+    )
 }
 
 private class SceneDraggableHandler(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 80f8c1c..7e0aa9c3 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -27,6 +27,10 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
 
 /**
  * [SceneTransitionLayout] is a container that automatically animates its content whenever its state
@@ -38,7 +42,8 @@
  * UI code.
  *
  * @param state the state of this layout.
- * @param edgeDetector the edge detector used to detect which edge a swipe is started from, if any.
+ * @param swipeSourceDetector the edge detector used to detect which edge a swipe is started from,
+ *   if any.
  * @param transitionInterceptionThreshold used during a scene transition. For the scene to be
  *   intercepted, the progress value must be above the threshold, and below (1 - threshold).
  * @param scenes the configuration of the different scenes of this layout.
@@ -48,14 +53,14 @@
 fun SceneTransitionLayout(
     state: SceneTransitionLayoutState,
     modifier: Modifier = Modifier,
-    edgeDetector: EdgeDetector = DefaultEdgeDetector,
+    swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
     @FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0f,
     scenes: SceneTransitionLayoutScope.() -> Unit,
 ) {
     SceneTransitionLayoutForTesting(
         state,
         modifier,
-        edgeDetector,
+        swipeSourceDetector,
         transitionInterceptionThreshold,
         onLayoutImpl = null,
         scenes,
@@ -76,7 +81,8 @@
  *   This is called when the user commits a transition to a new scene because of a [UserAction], for
  *   instance by triggering back navigation or by swiping to a new scene.
  * @param transitions the definition of the transitions used to animate a change of scene.
- * @param edgeDetector the edge detector used to detect which edge a swipe is started from, if any.
+ * @param swipeSourceDetector the source detector used to detect which source a swipe is started
+ *   from, if any.
  * @param transitionInterceptionThreshold used during a scene transition. For the scene to be
  *   intercepted, the progress value must be above the threshold, and below (1 - threshold).
  * @param scenes the configuration of the different scenes of this layout.
@@ -87,7 +93,7 @@
     onChangeScene: (SceneKey) -> Unit,
     transitions: SceneTransitions,
     modifier: Modifier = Modifier,
-    edgeDetector: EdgeDetector = DefaultEdgeDetector,
+    swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
     @FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0f,
     scenes: SceneTransitionLayoutScope.() -> Unit,
 ) {
@@ -95,7 +101,7 @@
     SceneTransitionLayout(
         state,
         modifier,
-        edgeDetector,
+        swipeSourceDetector,
         transitionInterceptionThreshold,
         scenes,
     )
@@ -113,7 +119,7 @@
      */
     fun scene(
         key: SceneKey,
-        userActions: Map<UserAction, SceneKey> = emptyMap(),
+        userActions: Map<UserAction, UserActionResult> = emptyMap(),
         content: @Composable SceneScope.() -> Unit,
     )
 }
@@ -335,7 +341,7 @@
 data class Swipe(
     val direction: SwipeDirection,
     val pointerCount: Int = 1,
-    val fromEdge: Edge? = null,
+    val fromSource: SwipeSource? = null,
 ) : UserAction {
     companion object {
         val Left = Swipe(SwipeDirection.Left)
@@ -353,6 +359,95 @@
 }
 
 /**
+ * The source of a Swipe.
+ *
+ * Important: This can be anything that can be returned by any [SwipeSourceDetector], but this must
+ * implement [equals] and [hashCode]. Note that those can be trivially implemented using data
+ * classes.
+ */
+interface SwipeSource {
+    // Require equals() and hashCode() to be implemented.
+    override fun equals(other: Any?): Boolean
+
+    override fun hashCode(): Int
+}
+
+interface SwipeSourceDetector {
+    /**
+     * Return the [SwipeSource] associated to [position] inside a layout of size [layoutSize], given
+     * [density] and [orientation].
+     */
+    fun source(
+        layoutSize: IntSize,
+        position: IntOffset,
+        density: Density,
+        orientation: Orientation,
+    ): SwipeSource?
+}
+
+/**
+ * The result of performing a [UserAction].
+ *
+ * Note: [UserActionResult] is implemented by [SceneKey], and you can also use [withDistance] to
+ * easily create a [UserActionResult] with a fixed distance:
+ * ```
+ * SceneTransitionLayout(...) {
+ *     scene(
+ *         Scenes.Foo,
+ *         userActions =
+ *             mapOf(
+ *                 Swipe.Right to Scene.Bar,
+ *                 Swipe.Down to Scene.Doe withDistance 100.dp,
+ *             )
+ *         )
+ *     ) { ... }
+ * }
+ * ```
+ */
+interface UserActionResult {
+    /** The scene we should be transitioning to during the [UserAction]. */
+    val toScene: SceneKey
+
+    /**
+     * The distance the action takes to animate from 0% to 100%.
+     *
+     * If `null`, a default distance will be used that depends on the [UserAction] performed.
+     */
+    val distance: UserActionDistance?
+}
+
+interface UserActionDistance {
+    /**
+     * Return the **absolute** distance of the user action given the size of the scene we are
+     * animating from and the [orientation].
+     */
+    fun Density.absoluteDistance(fromSceneSize: IntSize, orientation: Orientation): Float
+}
+
+/**
+ * A utility function to make it possible to define user actions with a distance using the syntax
+ * `Swipe.Up to Scene.foo withDistance 100.dp`
+ */
+infix fun Pair<UserAction, SceneKey>.withDistance(
+    distance: Dp
+): Pair<UserAction, UserActionResult> {
+    val scene = second
+    val distance = FixedDistance(distance)
+    return first to
+        object : UserActionResult {
+            override val toScene: SceneKey = scene
+            override val distance: UserActionDistance = distance
+        }
+}
+
+/** The user action has a fixed [absoluteDistance]. */
+private class FixedDistance(private val distance: Dp) : UserActionDistance {
+    override fun Density.absoluteDistance(fromSceneSize: IntSize, orientation: Orientation): Float {
+        return distance.toPx()
+    }
+}
+
+/**
  * An internal version of [SceneTransitionLayout] to be used for tests.
  *
  * Important: You should use this only in tests and if you need to access the underlying
@@ -362,7 +457,7 @@
 internal fun SceneTransitionLayoutForTesting(
     state: SceneTransitionLayoutState,
     modifier: Modifier = Modifier,
-    edgeDetector: EdgeDetector = DefaultEdgeDetector,
+    swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
     transitionInterceptionThreshold: Float = 0f,
     onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)? = null,
     scenes: SceneTransitionLayoutScope.() -> Unit,
@@ -373,7 +468,7 @@
         SceneTransitionLayoutImpl(
                 state = state as BaseSceneTransitionLayoutState,
                 density = density,
-                edgeDetector = edgeDetector,
+                swipeSourceDetector = swipeSourceDetector,
                 transitionInterceptionThreshold = transitionInterceptionThreshold,
                 builder = scenes,
                 coroutineScope = coroutineScope,
@@ -394,7 +489,7 @@
         }
 
         layoutImpl.density = density
-        layoutImpl.edgeDetector = edgeDetector
+        layoutImpl.swipeSourceDetector = swipeSourceDetector
     }
 
     layoutImpl.Content(modifier)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 7cc9d26..8c5a472 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -47,7 +47,7 @@
 internal class SceneTransitionLayoutImpl(
     internal val state: BaseSceneTransitionLayoutState,
     internal var density: Density,
-    internal var edgeDetector: EdgeDetector,
+    internal var swipeSourceDetector: SwipeSourceDetector,
     internal var transitionInterceptionThreshold: Float,
     builder: SceneTransitionLayoutScope.() -> Unit,
     private val coroutineScope: CoroutineScope,
@@ -140,7 +140,7 @@
         object : SceneTransitionLayoutScope {
                 override fun scene(
                     key: SceneKey,
-                    userActions: Map<UserAction, SceneKey>,
+                    userActions: Map<UserAction, UserActionResult>,
                     content: @Composable SceneScope.() -> Unit,
                 ) {
                     scenesToRemove.remove(key)
@@ -229,8 +229,10 @@
                 // Handle back events.
                 // TODO(b/290184746): Make sure that this works with SystemUI once we use
                 // SceneTransitionLayout in Flexiglass.
-                scene(state.transitionState.currentScene).userActions[Back]?.let { backScene ->
-                    BackHandler { with(state) { coroutineScope.onChangeScene(backScene) } }
+                scene(state.transitionState.currentScene).userActions[Back]?.let { result ->
+                    // TODO(b/290184746): Handle predictive back and use result.distance if
+                    // specified.
+                    BackHandler { with(state) { coroutineScope.onChangeScene(result.toScene) } }
                 }
 
                 Box {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index 0d3bc7d..b9c4ac0 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -17,40 +17,98 @@
 package com.android.compose.animation.scene
 
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.runtime.Stable
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.pointer.PointerEvent
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.node.DelegatingNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.PointerInputModifierNode
+import androidx.compose.ui.unit.IntSize
 
 /**
  * Configures the swipeable behavior of a [SceneTransitionLayout] depending on the current state.
  */
+@Stable
 internal fun Modifier.swipeToScene(gestureHandler: SceneGestureHandler): Modifier {
-    /** Whether swipe should be enabled in the given [orientation]. */
-    fun Scene.shouldEnableSwipes(orientation: Orientation): Boolean =
-        userActions.keys.any { it is Swipe && it.direction.orientation == orientation }
+    return this.then(SwipeToSceneElement(gestureHandler))
+}
 
-    val layoutImpl = gestureHandler.layoutImpl
-    val currentScene = layoutImpl.scene(layoutImpl.state.transitionState.currentScene)
-    val orientation = gestureHandler.orientation
-    val canSwipe = currentScene.shouldEnableSwipes(orientation)
-    val canOppositeSwipe =
-        currentScene.shouldEnableSwipes(
-            when (orientation) {
+private data class SwipeToSceneElement(
+    val gestureHandler: SceneGestureHandler,
+) : ModifierNodeElement<SwipeToSceneNode>() {
+    override fun create(): SwipeToSceneNode = SwipeToSceneNode(gestureHandler)
+
+    override fun update(node: SwipeToSceneNode) {
+        node.gestureHandler = gestureHandler
+    }
+}
+
+private class SwipeToSceneNode(
+    gestureHandler: SceneGestureHandler,
+) : DelegatingNode(), PointerInputModifierNode {
+    private val delegate =
+        delegate(
+            MultiPointerDraggableNode(
+                orientation = gestureHandler.orientation,
+                enabled = ::enabled,
+                startDragImmediately = ::startDragImmediately,
+                onDragStarted = gestureHandler.draggable::onDragStarted,
+                onDragDelta = gestureHandler.draggable::onDelta,
+                onDragStopped = gestureHandler.draggable::onDragStopped,
+            )
+        )
+
+    var gestureHandler: SceneGestureHandler = gestureHandler
+        set(value) {
+            if (value != field) {
+                field = value
+
+                // Make sure to update the delegate orientation. Note that this will automatically
+                // reset the underlying pointer input handler, so previous gestures will be
+                // cancelled.
+                delegate.orientation = value.orientation
+            }
+        }
+
+    override fun onPointerEvent(
+        pointerEvent: PointerEvent,
+        pass: PointerEventPass,
+        bounds: IntSize,
+    ) = delegate.onPointerEvent(pointerEvent, pass, bounds)
+
+    override fun onCancelPointerInput() = delegate.onCancelPointerInput()
+
+    private fun enabled(): Boolean {
+        return gestureHandler.isDrivingTransition ||
+            currentScene().shouldEnableSwipes(gestureHandler.orientation)
+    }
+
+    private fun currentScene(): Scene {
+        val layoutImpl = gestureHandler.layoutImpl
+        return layoutImpl.scene(layoutImpl.state.transitionState.currentScene)
+    }
+
+    /** Whether swipe should be enabled in the given [orientation]. */
+    private fun Scene.shouldEnableSwipes(orientation: Orientation): Boolean {
+        return userActions.keys.any { it is Swipe && it.direction.orientation == orientation }
+    }
+
+    private fun startDragImmediately(): Boolean {
+        // Immediately start the drag if this our transition is currently animating to a scene
+        // (i.e. the user released their input pointer after swiping in this orientation) and the
+        // user can't swipe in the other direction.
+        return gestureHandler.isDrivingTransition &&
+            gestureHandler.swipeTransition.isAnimatingOffset &&
+            !canOppositeSwipe()
+    }
+
+    private fun canOppositeSwipe(): Boolean {
+        val oppositeOrientation =
+            when (gestureHandler.orientation) {
                 Orientation.Vertical -> Orientation.Horizontal
                 Orientation.Horizontal -> Orientation.Vertical
             }
-        )
-
-    return multiPointerDraggable(
-        orientation = orientation,
-        enabled = gestureHandler.isDrivingTransition || canSwipe,
-        // Immediately start the drag if this our [transition] is currently animating to a scene
-        // (i.e. the user released their input pointer after swiping in this orientation) and the
-        // user can't swipe in the other direction.
-        startDragImmediately =
-            gestureHandler.isDrivingTransition &&
-                gestureHandler.swipeTransition.isAnimatingOffset &&
-                !canOppositeSwipe,
-        onDragStarted = gestureHandler.draggable::onDragStarted,
-        onDragDelta = gestureHandler.draggable::onDelta,
-        onDragStopped = gestureHandler.draggable::onDragStopped,
-    )
+        return currentScene().shouldEnableSwipes(oppositeOrientation)
+    }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index dc8505c..a764a527 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -320,11 +320,3 @@
         anchorHeight: Boolean = true,
     )
 }
-
-/** The edge of a [SceneTransitionLayout]. */
-enum class Edge {
-    Left,
-    Right,
-    Top,
-    Bottom,
-}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
index 2841bcf..ac11d30 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
@@ -22,6 +22,7 @@
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 import androidx.compose.ui.unit.Velocity
 import com.android.compose.ui.util.SpaceVectorConverter
+import kotlin.math.sign
 
 /**
  * This [NestedScrollConnection] waits for a child to scroll ([onPreScroll] or [onPostScroll]), and
@@ -117,7 +118,12 @@
             return Velocity.Zero
         }
 
-        onPriorityStart(available = Offset.Zero)
+        // The offset passed to onPriorityStart() must be != 0f, so we create a small offset of 1px
+        // given the available velocity.
+        // TODO(b/291053278): Remove canStartPostFling() and instead make it possible to define the
+        // overscroll behavior on the Scene level.
+        val smallOffset = Offset(available.x.sign, available.y.sign)
+        onPriorityStart(available = smallOffset)
 
         // This is the last event of a scroll gesture.
         return onPriorityStop(available)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt
index a68282a..cceaf57 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt
@@ -35,7 +35,7 @@
     @Test
     fun horizontalEdges() {
         fun horizontalEdge(position: Int): Edge? =
-            detector.edge(
+            detector.source(
                 layoutSize,
                 position = IntOffset(position, 0),
                 density,
@@ -53,7 +53,7 @@
     @Test
     fun verticalEdges() {
         fun verticalEdge(position: Int): Edge? =
-            detector.edge(
+            detector.source(
                 layoutSize,
                 position = IntOffset(0, position),
                 density,
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
index 066a3e4..88363ad 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
@@ -77,7 +77,7 @@
                 userActions =
                     mapOf(
                         Swipe.Up to SceneB,
-                        Swipe(SwipeDirection.Up, fromEdge = Edge.Bottom) to SceneA
+                        Swipe(SwipeDirection.Up, fromSource = Edge.Bottom) to SceneA
                     ),
             ) {
                 Text("SceneC")
@@ -90,7 +90,7 @@
             SceneTransitionLayoutImpl(
                     state = layoutState,
                     density = Density(1f),
-                    edgeDetector = DefaultEdgeDetector,
+                    swipeSourceDetector = DefaultEdgeDetector,
                     transitionInterceptionThreshold = transitionInterceptionThreshold,
                     builder = scenesBuilder,
                     coroutineScope = coroutineScope,
@@ -192,16 +192,14 @@
 
     @Test
     fun onDragStarted_shouldStartATransition() = runGestureTest {
-        draggable.onDragStarted()
+        draggable.onDragStarted(overSlop = down(0.1f))
         assertTransition(currentScene = SceneA)
     }
 
     @Test
     fun afterSceneTransitionIsStarted_interceptDragEvents() = runGestureTest {
-        draggable.onDragStarted()
+        draggable.onDragStarted(overSlop = down(0.1f))
         assertTransition(currentScene = SceneA)
-
-        draggable.onDelta(pixels = down(0.1f))
         assertThat(progress).isEqualTo(0.1f)
 
         draggable.onDelta(pixels = down(0.1f))
@@ -210,10 +208,7 @@
 
     @Test
     fun onDragStoppedAfterDrag_velocityLowerThanThreshold_remainSameScene() = runGestureTest {
-        draggable.onDragStarted()
-        assertTransition(currentScene = SceneA)
-
-        draggable.onDelta(pixels = down(0.1f))
+        draggable.onDragStarted(overSlop = down(0.1f))
         assertTransition(currentScene = SceneA)
 
         draggable.onDragStopped(
@@ -228,10 +223,7 @@
 
     @Test
     fun onDragStoppedAfterDrag_velocityAtLeastThreshold_goToNextScene() = runGestureTest {
-        draggable.onDragStarted()
-        assertTransition(currentScene = SceneA)
-
-        draggable.onDelta(pixels = down(0.1f))
+        draggable.onDragStarted(overSlop = down(0.1f))
         assertTransition(currentScene = SceneA)
 
         draggable.onDragStopped(velocity = velocityThreshold)
@@ -245,7 +237,7 @@
 
     @Test
     fun onDragStoppedAfterStarted_returnToIdle() = runGestureTest {
-        draggable.onDragStarted()
+        draggable.onDragStarted(overSlop = down(0.1f))
         assertTransition(currentScene = SceneA)
 
         draggable.onDragStopped(velocity = 0f)
@@ -256,8 +248,7 @@
     @Test
     fun onDragReversedDirection_changeToScene() = runGestureTest {
         // Drag A -> B with progress 0.6
-        draggable.onDragStarted()
-        draggable.onDelta(up(0.6f))
+        draggable.onDragStarted(overSlop = up(0.6f))
         assertTransition(
             currentScene = SceneA,
             fromScene = SceneA,
@@ -366,8 +357,7 @@
     @Test
     fun onAccelaratedScroll_scrollToThirdScene() = runGestureTest {
         // Drag A -> B with progress 0.2
-        draggable.onDragStarted()
-        draggable.onDelta(up(0.2f))
+        draggable.onDragStarted(overSlop = up(0.2f))
         assertTransition(
             currentScene = SceneA,
             fromScene = SceneA,
@@ -401,9 +391,7 @@
 
     @Test
     fun onAccelaratedScrollBothTargetsBecomeNull_settlesToIdle() = runGestureTest {
-        draggable.onDragStarted()
-        draggable.onDelta(up(0.2f))
-
+        draggable.onDragStarted(overSlop = up(0.2f))
         draggable.onDelta(up(0.2f))
         draggable.onDragStopped(velocity = -velocityThreshold)
         assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
@@ -459,16 +447,14 @@
         draggable.onDragStopped(down(0.1f))
 
         // now target changed to C for new drag that started before previous drag settled to Idle
-        draggable.onDragStarted(up(0.1f))
+        draggable.onDragStarted(overSlop = 0f)
+        draggable.onDelta(up(0.1f))
         assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.3f)
     }
 
     @Test
     fun startGestureDuringAnimatingOffset_shouldImmediatelyStopTheAnimation() = runGestureTest {
-        draggable.onDragStarted()
-        assertTransition(currentScene = SceneA)
-
-        draggable.onDelta(pixels = down(0.1f))
+        draggable.onDragStarted(overSlop = down(0.1f))
         assertTransition(currentScene = SceneA)
 
         draggable.onDragStopped(
@@ -759,10 +745,8 @@
     @Test
     fun startNestedScrollWhileDragging() = runGestureTest {
         val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways)
-        draggable.onDragStarted()
+        draggable.onDragStarted(overSlop = down(0.1f))
         assertTransition(currentScene = SceneA)
-
-        draggable.onDelta(down(0.1f))
         assertThat(progress).isEqualTo(0.1f)
 
         // now we can intercept the scroll events
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index 1ec3c8b..9403358 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -17,6 +17,7 @@
 package com.android.compose.animation.scene
 
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.size
 import androidx.compose.runtime.Composable
@@ -89,8 +90,8 @@
                     mapOf(
                         Swipe.Down to TestScenes.SceneA,
                         Swipe(SwipeDirection.Down, pointerCount = 2) to TestScenes.SceneB,
-                        Swipe(SwipeDirection.Right, fromEdge = Edge.Left) to TestScenes.SceneB,
-                        Swipe(SwipeDirection.Down, fromEdge = Edge.Top) to TestScenes.SceneB,
+                        Swipe(SwipeDirection.Right, fromSource = Edge.Left) to TestScenes.SceneB,
+                        Swipe(SwipeDirection.Down, fromSource = Edge.Top) to TestScenes.SceneB,
                     ),
             ) {
                 Box(Modifier.fillMaxSize())
@@ -349,4 +350,46 @@
         assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
         assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC)
     }
+
+    @Test
+    fun swipeDistance() {
+        // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
+        // detected as a drag event.
+        var touchSlop = 0f
+
+        val layoutState = MutableSceneTransitionLayoutState(TestScenes.SceneA)
+        val verticalSwipeDistance = 50.dp
+        assertThat(verticalSwipeDistance).isNotEqualTo(LayoutHeight)
+
+        rule.setContent {
+            touchSlop = LocalViewConfiguration.current.touchSlop
+
+            SceneTransitionLayout(
+                state = layoutState,
+                modifier = Modifier.size(LayoutWidth, LayoutHeight)
+            ) {
+                scene(
+                    TestScenes.SceneA,
+                    userActions =
+                        mapOf(Swipe.Down to TestScenes.SceneB withDistance verticalSwipeDistance),
+                ) {
+                    Spacer(Modifier.fillMaxSize())
+                }
+                scene(TestScenes.SceneB) { Spacer(Modifier.fillMaxSize()) }
+            }
+        }
+
+        assertThat(layoutState.currentTransition).isNull()
+
+        // Swipe by half of verticalSwipeDistance.
+        rule.onRoot().performTouchInput {
+            down(middleTop)
+            moveBy(Offset(0f, touchSlop + (verticalSwipeDistance / 2).toPx()), delayMillis = 1_000)
+        }
+
+        // We should be at 50%
+        val transition = layoutState.currentTransition
+        assertThat(transition).isNotNull()
+        assertThat(transition!!.progress).isEqualTo(0.5f)
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 030d41d..c82688c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -203,11 +203,17 @@
         whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(true)
 
         featureFlags = FakeFeatureFlags()
-        featureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
         featureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
         featureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
 
-        mSetFlagsRule.enableFlags(AConfigFlags.FLAG_REVAMPED_BOUNCER_MESSAGES)
+        mSetFlagsRule.enableFlags(
+            AConfigFlags.FLAG_REVAMPED_BOUNCER_MESSAGES,
+        )
+        mSetFlagsRule.disableFlags(
+            FLAG_SIDEFPS_CONTROLLER_REFACTOR,
+            AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR
+        )
+
         keyguardPasswordViewController =
             KeyguardPasswordViewController(
                 keyguardPasswordView,
@@ -238,7 +244,6 @@
         sceneInteractor.setTransitionState(sceneTransitionStateFlow)
         deviceEntryInteractor = kosmos.deviceEntryInteractor
 
-        mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
         underTest =
             KeyguardSecurityContainerController(
                 view,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
new file mode 100644
index 0000000..9287edf
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.accessibility.data.repository
+
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AccessibilityQsShortcutsRepositoryImplTest : SysuiTestCase() {
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+    private val secureSettings = FakeSettings()
+
+    private val userA11yQsShortcutsRepositoryFactory =
+        object : UserA11yQsShortcutsRepository.Factory {
+            override fun create(userId: Int): UserA11yQsShortcutsRepository {
+                return UserA11yQsShortcutsRepository(
+                    userId,
+                    secureSettings,
+                    testScope.backgroundScope,
+                    testDispatcher,
+                )
+            }
+        }
+
+    private val underTest =
+        AccessibilityQsShortcutsRepositoryImpl(userA11yQsShortcutsRepositoryFactory)
+
+    @Test
+    fun a11yQsShortcutTargetsForCorrectUsers() =
+        testScope.runTest {
+            val user0 = 0
+            val targetsForUser0 = setOf("a", "b", "c")
+            val user1 = 1
+            val targetsForUser1 = setOf("A")
+            val targetsFromUser0 by collectLastValue(underTest.a11yQsShortcutTargets(user0))
+            val targetsFromUser1 by collectLastValue(underTest.a11yQsShortcutTargets(user1))
+
+            storeA11yQsShortcutTargetsForUser(targetsForUser0, user0)
+            storeA11yQsShortcutTargetsForUser(targetsForUser1, user1)
+
+            assertThat(targetsFromUser0).isEqualTo(targetsForUser0)
+            assertThat(targetsFromUser1).isEqualTo(targetsForUser1)
+        }
+
+    private fun storeA11yQsShortcutTargetsForUser(a11yQsTargets: Set<String>, forUser: Int) {
+        secureSettings.putStringForUser(
+            SETTING_NAME,
+            a11yQsTargets.joinToString(separator = ":"),
+            forUser
+        )
+    }
+
+    companion object {
+        private const val SETTING_NAME = Settings.Secure.ACCESSIBILITY_QS_TARGETS
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt
new file mode 100644
index 0000000..ce22e28
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.accessibility.data.repository
+
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UserA11yQsShortcutsRepositoryTest : SysuiTestCase() {
+    private val secureSettings = FakeSettings()
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    private val underTest =
+        UserA11yQsShortcutsRepository(
+            USER_ID,
+            secureSettings,
+            testScope.backgroundScope,
+            testDispatcher
+        )
+
+    @Test
+    fun targetsMatchesSetting() =
+        testScope.runTest {
+            val observedTargets by collectLastValue(underTest.targets)
+            val a11yQsTargets = setOf("a", "b", "c")
+            secureSettings.putStringForUser(
+                SETTING_NAME,
+                a11yQsTargets.joinToString(SEPARATOR),
+                USER_ID
+            )
+
+            assertThat(observedTargets).isEqualTo(a11yQsTargets)
+        }
+
+    companion object {
+        private const val USER_ID = 0
+        private const val SEPARATOR = ":"
+        private const val SETTING_NAME = Settings.Secure.ACCESSIBILITY_QS_TARGETS
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index bb3429e..c979ca6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -30,7 +30,6 @@
 import com.android.systemui.communal.shared.CommunalWidgetHost
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.communal.widgets.CommunalAppWidgetHost
-import com.android.systemui.communal.widgets.WidgetConfigurator
 import com.android.systemui.communal.widgets.widgetConfiguratorFail
 import com.android.systemui.communal.widgets.widgetConfiguratorSuccess
 import com.android.systemui.coroutines.collectLastValue
@@ -45,8 +44,7 @@
 import com.google.common.truth.Truth.assertThat
 import java.util.Optional
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -62,24 +60,17 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
-    @Mock private lateinit var appWidgetManagerOptional: Optional<AppWidgetManager>
-
     @Mock private lateinit var appWidgetManager: AppWidgetManager
-
     @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
-
     @Mock private lateinit var stopwatchProviderInfo: AppWidgetProviderInfo
-
     @Mock private lateinit var providerInfoA: AppWidgetProviderInfo
-
     @Mock private lateinit var communalWidgetHost: CommunalWidgetHost
-
     @Mock private lateinit var communalWidgetDao: CommunalWidgetDao
 
     private lateinit var logBuffer: LogBuffer
+    private lateinit var fakeWidgets: MutableStateFlow<Map<CommunalItemRank, CommunalWidgetItem>>
 
     private val kosmos = testKosmos()
-    private val testDispatcher = kosmos.testDispatcher
     private val testScope = kosmos.testScope
 
     private val fakeAllowlist =
@@ -94,7 +85,7 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-
+        fakeWidgets = MutableStateFlow(emptyMap())
         logBuffer = logcatLogBuffer(name = "CommunalWidgetRepoImplTest")
 
         setAppWidgetIds(emptyList())
@@ -102,13 +93,11 @@
         overrideResource(R.array.config_communalWidgetAllowlist, fakeAllowlist.toTypedArray())
 
         whenever(stopwatchProviderInfo.loadLabel(any())).thenReturn("Stopwatch")
-        whenever(communalWidgetDao.getWidgets()).thenReturn(flowOf(emptyMap()))
-        whenever(appWidgetManagerOptional.isPresent).thenReturn(true)
-        whenever(appWidgetManagerOptional.get()).thenReturn(appWidgetManager)
+        whenever(communalWidgetDao.getWidgets()).thenReturn(fakeWidgets)
 
         underTest =
             CommunalWidgetRepositoryImpl(
-                appWidgetManagerOptional,
+                Optional.of(appWidgetManager),
                 appWidgetHost,
                 testScope.backgroundScope,
                 kosmos.testDispatcher,
@@ -119,30 +108,16 @@
     }
 
     @Test
-    fun neverQueryDbForWidgets_whenHostIsInactive() =
+    fun communalWidgets_queryWidgetsFromDb() =
         testScope.runTest {
-            underTest.updateAppWidgetHostActive(false)
-            underTest.communalWidgets.launchIn(testScope.backgroundScope)
-            runCurrent()
-
-            verify(communalWidgetDao, never()).getWidgets()
-        }
-
-    @Test
-    fun communalWidgets_whenHostIsActive_queryWidgetsFromDb() =
-        testScope.runTest {
-            underTest.updateAppWidgetHostActive(true)
-
             val communalItemRankEntry = CommunalItemRank(uid = 1L, rank = 1)
             val communalWidgetItemEntry = CommunalWidgetItem(uid = 1L, 1, "pk_name/cls_name", 1L)
-            whenever(communalWidgetDao.getWidgets())
-                .thenReturn(flowOf(mapOf(communalItemRankEntry to communalWidgetItemEntry)))
+            fakeWidgets.value = mapOf(communalItemRankEntry to communalWidgetItemEntry)
             whenever(appWidgetManager.getAppWidgetInfo(anyInt())).thenReturn(providerInfoA)
 
             installedProviders(listOf(stopwatchProviderInfo))
 
             val communalWidgets by collectLastValue(underTest.communalWidgets)
-            runCurrent()
             verify(communalWidgetDao).getWidgets()
             assertThat(communalWidgets)
                 .containsExactly(
@@ -157,8 +132,6 @@
     @Test
     fun addWidget_allocateId_bindWidget_andAddToDb() =
         testScope.runTest {
-            underTest.updateAppWidgetHostActive(true)
-
             val provider = ComponentName("pkg_name", "cls_name")
             val id = 1
             val priority = 1
@@ -176,8 +149,6 @@
     @Test
     fun addWidget_configurationFails_doNotAddWidgetToDb() =
         testScope.runTest {
-            underTest.updateAppWidgetHostActive(true)
-
             val provider = ComponentName("pkg_name", "cls_name")
             val id = 1
             val priority = 1
@@ -195,23 +166,13 @@
     @Test
     fun addWidget_configurationThrowsError_doNotAddWidgetToDb() =
         testScope.runTest {
-            underTest.updateAppWidgetHostActive(true)
-
             val provider = ComponentName("pkg_name", "cls_name")
             val id = 1
             val priority = 1
             whenever(communalWidgetHost.getAppWidgetInfo(id))
                 .thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
             whenever(communalWidgetHost.allocateIdAndBindWidget(provider)).thenReturn(id)
-            underTest.addWidget(
-                provider,
-                priority,
-                object : WidgetConfigurator {
-                    override suspend fun configureWidget(appWidgetId: Int): Boolean {
-                        throw IllegalStateException("some error")
-                    }
-                }
-            )
+            underTest.addWidget(provider, priority) { throw IllegalStateException("some error") }
             runCurrent()
 
             verify(communalWidgetHost).allocateIdAndBindWidget(provider)
@@ -222,8 +183,6 @@
     @Test
     fun addWidget_configurationNotRequired_doesNotConfigure_addWidgetToDb() =
         testScope.runTest {
-            underTest.updateAppWidgetHostActive(true)
-
             val provider = ComponentName("pkg_name", "cls_name")
             val id = 1
             val priority = 1
@@ -241,8 +200,6 @@
     @Test
     fun deleteWidget_removeWidgetId_andDeleteFromDb() =
         testScope.runTest {
-            underTest.updateAppWidgetHostActive(true)
-
             val id = 1
             underTest.deleteWidget(id)
             runCurrent()
@@ -254,8 +211,6 @@
     @Test
     fun reorderWidgets_queryDb() =
         testScope.runTest {
-            underTest.updateAppWidgetHostActive(true)
-
             val widgetIdToPriorityMap = mapOf(104 to 1, 103 to 2, 101 to 3)
             underTest.updateWidgetOrder(widgetIdToPriorityMap)
             runCurrent()
@@ -263,28 +218,6 @@
             verify(communalWidgetDao).updateWidgetOrder(widgetIdToPriorityMap)
         }
 
-    @Test
-    fun appWidgetHost_startListening() =
-        testScope.runTest {
-            verify(appWidgetHost, never()).startListening()
-
-            underTest.updateAppWidgetHostActive(true)
-
-            verify(appWidgetHost).startListening()
-        }
-
-    @Test
-    fun appWidgetHost_stopListening() =
-        testScope.runTest {
-            underTest.updateAppWidgetHostActive(true)
-
-            verify(appWidgetHost).startListening()
-
-            underTest.updateAppWidgetHostActive(false)
-
-            verify(appWidgetHost).stopListening()
-        }
-
     private fun installedProviders(providers: List<AppWidgetProviderInfo>) {
         whenever(appWidgetManager.installedProviders).thenReturn(providers)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
index e821673..6a3fc2a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
@@ -80,15 +80,4 @@
 
             assertThat(isCommunalAvailable).isFalse()
         }
-
-    @Test
-    fun updateAppWidgetHostActive_whenStorageUnlock_false() =
-        testScope.runTest {
-            assertThat(widgetRepository.isHostActive()).isFalse()
-
-            keyguardRepository.setIsEncryptedOrLockdown(false)
-            runCurrent()
-
-            assertThat(widgetRepository.isHostActive()).isFalse()
-        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 1b7117f..a083e7c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -172,20 +172,6 @@
         }
 
     @Test
-    fun updateAppWidgetHostActive_uponStorageUnlockAsMainUser_true() =
-        testScope.runTest {
-            collectLastValue(underTest.isCommunalAvailable)
-            assertThat(widgetRepository.isHostActive()).isFalse()
-
-            keyguardRepository.setIsEncryptedOrLockdown(false)
-            userRepository.setSelectedUserInfo(mainUser)
-            keyguardRepository.setKeyguardShowing(true)
-            runCurrent()
-
-            assertThat(widgetRepository.isHostActive()).isTrue()
-        }
-
-    @Test
     fun widget_tutorialCompletedAndWidgetsAvailable_showWidgetContent() =
         testScope.runTest {
             // Keyguard showing, and tutorial completed.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
new file mode 100644
index 0000000..112b0c7
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
@@ -0,0 +1,132 @@
+/*
+ * 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.communal.widgets
+
+import android.content.pm.UserInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalAppWidgetHostStartableTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
+    @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
+
+    private lateinit var underTest: CommunalAppWidgetHostStartable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO))
+
+        underTest =
+            CommunalAppWidgetHostStartable(
+                appWidgetHost,
+                kosmos.communalInteractor,
+                kosmos.applicationCoroutineScope,
+                kosmos.testDispatcher,
+            )
+    }
+
+    @Test
+    fun editModeShowingStartsAppWidgetHost() =
+        with(kosmos) {
+            testScope.runTest {
+                setCommunalAvailable(false)
+                communalInteractor.setEditModeOpen(true)
+                verify(appWidgetHost, never()).startListening()
+
+                underTest.start()
+                runCurrent()
+
+                verify(appWidgetHost).startListening()
+                verify(appWidgetHost, never()).stopListening()
+
+                communalInteractor.setEditModeOpen(false)
+                runCurrent()
+
+                verify(appWidgetHost).stopListening()
+            }
+        }
+
+    @Test
+    fun communalShowingStartsAppWidgetHost() =
+        with(kosmos) {
+            testScope.runTest {
+                setCommunalAvailable(true)
+                communalInteractor.setEditModeOpen(false)
+                verify(appWidgetHost, never()).startListening()
+
+                underTest.start()
+                runCurrent()
+
+                verify(appWidgetHost).startListening()
+                verify(appWidgetHost, never()).stopListening()
+
+                setCommunalAvailable(false)
+                runCurrent()
+
+                verify(appWidgetHost).stopListening()
+            }
+        }
+
+    @Test
+    fun communalAndEditModeNotShowingNeverStartListening() =
+        with(kosmos) {
+            testScope.runTest {
+                setCommunalAvailable(false)
+                communalInteractor.setEditModeOpen(false)
+
+                underTest.start()
+                runCurrent()
+
+                verify(appWidgetHost, never()).startListening()
+                verify(appWidgetHost, never()).stopListening()
+            }
+        }
+
+    private suspend fun setCommunalAvailable(available: Boolean) =
+        with(kosmos) {
+            fakeKeyguardRepository.setIsEncryptedOrLockdown(!available)
+            fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
+            fakeKeyguardRepository.setKeyguardShowing(true)
+        }
+
+    private companion object {
+        val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 6a14220..6808f5d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -38,6 +38,7 @@
 import com.android.internal.logging.InstanceId.fakeInstanceId
 import com.android.internal.logging.UiEventLogger
 import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.Flags as AConfigFlags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
 import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
@@ -62,7 +63,6 @@
 import com.android.systemui.display.data.repository.display
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags.KEYGUARD_WM_STATE_REFACTOR
 import com.android.systemui.keyguard.data.repository.BiometricType
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue
@@ -194,7 +194,7 @@
         biometricSettingsRepository = FakeBiometricSettingsRepository()
         deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
         trustRepository = FakeTrustRepository()
-        featureFlags = FakeFeatureFlags().apply { set(KEYGUARD_WM_STATE_REFACTOR, false) }
+        featureFlags = FakeFeatureFlags()
 
         powerRepository = FakePowerRepository()
         powerInteractor =
@@ -252,6 +252,10 @@
             .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = true)))
         whenever(bypassController.bypassEnabled).thenReturn(true)
         underTest = createDeviceEntryFaceAuthRepositoryImpl(faceManager, bypassController)
+
+        mSetFlagsRule.disableFlags(
+            AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
+        )
     }
 
     private fun createDeviceEntryFaceAuthRepositoryImpl(
@@ -301,7 +305,6 @@
             faceAuthBuffer,
             keyguardTransitionInteractor,
             displayStateInteractor,
-            featureFlags,
             dumpManager,
         )
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorTest.kt
new file mode 100644
index 0000000..88ad3f3
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorTest.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.deviceentry.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AuthRippleInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val deviceEntrySourceInteractor = kosmos.deviceEntrySourceInteractor
+    private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
+    private val keyguardRepository = kosmos.fakeKeyguardRepository
+    private val underTest = kosmos.authRippleInteractor
+
+    @Test
+    fun enteringDeviceFromDeviceEntryIcon_udfpsNotSupported_doesNotShowAuthRipple() =
+        testScope.runTest {
+            val showUnlockRipple by collectLastValue(underTest.showUnlockRipple)
+            fingerprintPropertyRepository.supportsRearFps()
+            keyguardRepository.setKeyguardDismissible(true)
+            runCurrent()
+            deviceEntrySourceInteractor.attemptEnterDeviceFromDeviceEntryIcon()
+            assertThat(showUnlockRipple).isNull()
+        }
+
+    @Test
+    fun enteringDeviceFromDeviceEntryIcon_udfpsSupported_showsAuthRipple() =
+        testScope.runTest {
+            val showUnlockRipple by collectLastValue(underTest.showUnlockRipple)
+            fingerprintPropertyRepository.supportsUdfps()
+            keyguardRepository.setKeyguardDismissible(true)
+            runCurrent()
+            deviceEntrySourceInteractor.attemptEnterDeviceFromDeviceEntryIcon()
+            assertThat(showUnlockRipple).isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR)
+        }
+
+    @Test
+    fun faceUnlocked_showsAuthRipple() =
+        testScope.runTest {
+            val showUnlockRipple by collectLastValue(underTest.showUnlockRipple)
+            keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FACE_SENSOR)
+            keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+            assertThat(showUnlockRipple).isEqualTo(BiometricUnlockSource.FACE_SENSOR)
+        }
+
+    @Test
+    fun fingerprintUnlocked_showsAuthRipple() =
+        testScope.runTest {
+            val showUnlockRippleFromBiometricUnlock by collectLastValue(underTest.showUnlockRipple)
+            keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FINGERPRINT_SENSOR)
+            keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+            assertThat(showUnlockRippleFromBiometricUnlock)
+                .isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt
new file mode 100644
index 0000000..d216fa0
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceEntrySourceInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val keyguardRepository = kosmos.fakeKeyguardRepository
+    private val underTest = kosmos.deviceEntrySourceInteractor
+
+    @Test
+    fun deviceEntryFromFaceUnlock() =
+        testScope.runTest {
+            val deviceEntryFromBiometricAuthentication by
+                collectLastValue(underTest.deviceEntryFromBiometricSource)
+            keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FACE_SENSOR)
+            keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+            runCurrent()
+            assertThat(deviceEntryFromBiometricAuthentication)
+                .isEqualTo(BiometricUnlockSource.FACE_SENSOR)
+        }
+
+    @Test
+    fun deviceEntryFromFingerprintUnlock() = runTest {
+        val deviceEntryFromBiometricAuthentication by
+            collectLastValue(underTest.deviceEntryFromBiometricSource)
+        keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FINGERPRINT_SENSOR)
+        keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+        runCurrent()
+        assertThat(deviceEntryFromBiometricAuthentication)
+            .isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR)
+    }
+
+    @Test
+    fun noDeviceEntry() = runTest {
+        val deviceEntryFromBiometricAuthentication by
+            collectLastValue(underTest.deviceEntryFromBiometricSource)
+        keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FINGERPRINT_SENSOR)
+        // doesn't dismiss keyguard:
+        keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.ONLY_WAKE)
+        runCurrent()
+        assertThat(deviceEntryFromBiometricAuthentication).isNull()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index dc8b97a..78ae8b1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -238,10 +238,10 @@
         }
 
     @Test
-    fun isKeyguardUnlocked() =
+    fun isKeyguardDismissible() =
         testScope.runTest {
             whenever(keyguardStateController.isUnlocked).thenReturn(false)
-            val isKeyguardUnlocked by collectLastValue(underTest.isKeyguardUnlocked)
+            val isKeyguardUnlocked by collectLastValue(underTest.isKeyguardDismissible)
 
             runCurrent()
             assertThat(isKeyguardUnlocked).isFalse()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
index eb845b2..d9f24b3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
@@ -35,7 +35,6 @@
 import com.android.systemui.common.data.repository.packageChangeRepository
 import com.android.systemui.common.data.shared.model.PackageChangeModel
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
@@ -44,6 +43,8 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -52,6 +53,7 @@
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
@@ -82,7 +84,7 @@
         underTest =
             InstalledTilesComponentRepositoryImpl(
                 context,
-                kosmos.testDispatcher,
+                testScope.backgroundScope,
                 kosmos.packageChangeRepository
             )
     }
@@ -103,6 +105,7 @@
                 .thenReturn(listOf(resolveInfo))
 
             val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+            runCurrent()
 
             assertThat(componentNames).containsExactly(TEST_COMPONENT)
         }
@@ -115,6 +118,8 @@
                 ResolveInfo(TEST_COMPONENT, hasPermission = true, defaultEnabled = true)
 
             val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+            runCurrent()
+
             assertThat(componentNames).isEmpty()
 
             whenever(
@@ -126,6 +131,7 @@
                 )
                 .thenReturn(listOf(resolveInfo))
             kosmos.fakePackageChangeRepository.notifyChange(PackageChangeModel.Empty)
+            runCurrent()
 
             assertThat(componentNames).containsExactly(TEST_COMPONENT)
         }
@@ -146,6 +152,8 @@
                 .thenReturn(listOf(resolveInfo))
 
             val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+            runCurrent()
+
             assertThat(componentNames).isEmpty()
         }
 
@@ -165,6 +173,8 @@
                 .thenReturn(listOf(resolveInfo))
 
             val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+            runCurrent()
+
             assertThat(componentNames).isEmpty()
         }
 
@@ -210,10 +220,31 @@
                 .thenReturn(listOf(resolveInfo))
 
             val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+            runCurrent()
 
             assertThat(componentNames).containsExactly(TEST_COMPONENT)
         }
 
+    @Test
+    fun loadComponentsForSameUserTwice_returnsSameFlow() =
+        testScope.runTest {
+            val flowForUser1 = underTest.getInstalledTilesComponents(1)
+            val flowForUser1TheSecondTime = underTest.getInstalledTilesComponents(1)
+            runCurrent()
+
+            assertThat(flowForUser1TheSecondTime).isEqualTo(flowForUser1)
+        }
+
+    @Test
+    fun loadComponentsForDifferentUsers_returnsDifferentFlow() =
+        testScope.runTest {
+            val flowForUser1 = underTest.getInstalledTilesComponents(1)
+            val flowForUser2 = underTest.getInstalledTilesComponents(2)
+            runCurrent()
+
+            assertThat(flowForUser2).isNotEqualTo(flowForUser1)
+        }
+
     companion object {
         private val INTENT = Intent(TileService.ACTION_QS_TILE)
         private val FLAGS =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt
new file mode 100644
index 0000000..311122d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.qs.pipeline.domain.autoaddable
+
+import android.content.ComponentName
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.view.accessibility.Flags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.accessibility.AccessibilityShortcutController
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.ColorCorrectionTile
+import com.android.systemui.qs.tiles.ColorInversionTile
+import com.android.systemui.qs.tiles.OneHandedModeTile
+import com.android.systemui.qs.tiles.ReduceBrightColorsTile
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class A11yShortcutAutoAddableListTest : SysuiTestCase() {
+
+    private val factory =
+        object : A11yShortcutAutoAddable.Factory {
+            override fun create(
+                spec: TileSpec,
+                componentName: ComponentName
+            ): A11yShortcutAutoAddable {
+                return A11yShortcutAutoAddable(mock(), mock(), spec, componentName)
+            }
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+    fun getA11yShortcutAutoAddables_withA11yQsShortcutFlagOff_emptyResult() {
+        val autoAddables = A11yShortcutAutoAddableList.getA11yShortcutAutoAddables(factory)
+
+        assertThat(autoAddables).isEmpty()
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+    fun getA11yShortcutAutoAddables_withA11yQsShortcutFlagOn_correctAutoAddables() {
+        val expected =
+            setOf(
+                factory.create(
+                    TileSpec.create(ColorCorrectionTile.TILE_SPEC),
+                    AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME
+                ),
+                factory.create(
+                    TileSpec.create(ColorInversionTile.TILE_SPEC),
+                    AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME
+                ),
+                factory.create(
+                    TileSpec.create(OneHandedModeTile.TILE_SPEC),
+                    AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME
+                ),
+                factory.create(
+                    TileSpec.create(ReduceBrightColorsTile.TILE_SPEC),
+                    AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME
+                ),
+            )
+
+        val autoAddables = A11yShortcutAutoAddableList.getA11yShortcutAutoAddables(factory)
+
+        assertThat(autoAddables).isNotEmpty()
+        assertThat(autoAddables).containsExactlyElementsIn(expected)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableTest.kt
new file mode 100644
index 0000000..3b33a43
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableTest.kt
@@ -0,0 +1,175 @@
+/*
+ * 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.qs.pipeline.domain.autoaddable
+
+import android.content.ComponentName
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.data.repository.FakeAccessibilityQsShortcutsRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class A11yShortcutAutoAddableTest : SysuiTestCase() {
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    private val a11yQsShortcutsRepository = FakeAccessibilityQsShortcutsRepository()
+    private val underTest =
+        A11yShortcutAutoAddable(a11yQsShortcutsRepository, testDispatcher, SPEC, TARGET_COMPONENT)
+
+    @Test
+    fun settingNotSet_noSignal() =
+        testScope.runTest {
+            val signal by collectLastValue(underTest.autoAddSignal(USER_ID))
+
+            assertThat(signal).isNull() // null means no emitted value
+        }
+
+    @Test
+    fun settingSetWithTarget_addSignal() =
+        testScope.runTest {
+            val signal by collectLastValue(underTest.autoAddSignal(USER_ID))
+            assertThat(signal).isNull()
+
+            a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+                USER_ID,
+                setOf(TARGET_COMPONENT_FLATTEN)
+            )
+
+            assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+        }
+
+    @Test
+    fun settingSetWithoutTarget_removeSignal() =
+        testScope.runTest {
+            val signal by collectLastValue(flow = underTest.autoAddSignal(USER_ID))
+            assertThat(signal).isNull()
+
+            a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+                USER_ID,
+                setOf(OTHER_COMPONENT_FLATTEN)
+            )
+
+            assertThat(signal).isEqualTo(AutoAddSignal.Remove(SPEC))
+        }
+
+    @Test
+    fun settingSetWithMultipleComponents_containsTarget_addSignal() =
+        testScope.runTest {
+            val signal by collectLastValue(underTest.autoAddSignal(USER_ID))
+            assertThat(signal).isNull()
+
+            a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+                USER_ID,
+                setOf(OTHER_COMPONENT_FLATTEN, TARGET_COMPONENT_FLATTEN)
+            )
+
+            assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+        }
+
+    @Test
+    fun settingSetWithMultipleComponents_doesNotContainTarget_removeSignal() =
+        testScope.runTest {
+            val signal by collectLastValue(underTest.autoAddSignal(USER_ID))
+            assertThat(signal).isNull()
+
+            a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+                USER_ID,
+                setOf(OTHER_COMPONENT_FLATTEN, OTHER_COMPONENT_FLATTEN)
+            )
+
+            assertThat(signal).isEqualTo(AutoAddSignal.Remove(SPEC))
+        }
+
+    @Test
+    fun multipleChangesWithTarget_onlyOneAddSignal() =
+        testScope.runTest {
+            val signals by collectValues(underTest.autoAddSignal(USER_ID))
+            assertThat(signals).isEmpty()
+
+            repeat(3) {
+                a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+                    USER_ID,
+                    setOf(TARGET_COMPONENT_FLATTEN)
+                )
+            }
+
+            assertThat(signals.size).isEqualTo(1)
+            assertThat(signals[0]).isEqualTo(AutoAddSignal.Add(SPEC))
+        }
+
+    @Test
+    fun multipleChangesWithoutTarget_onlyOneRemoveSignal() =
+        testScope.runTest {
+            val signals by collectValues(underTest.autoAddSignal(USER_ID))
+            assertThat(signals).isEmpty()
+
+            repeat(3) {
+                a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+                    USER_ID,
+                    setOf("$OTHER_COMPONENT_FLATTEN$it")
+                )
+            }
+
+            assertThat(signals.size).isEqualTo(1)
+            assertThat(signals[0]).isEqualTo(AutoAddSignal.Remove(SPEC))
+        }
+
+    @Test
+    fun settingSetWithTargetForUsers_onlySignalInThatUser() =
+        testScope.runTest {
+            val otherUserId = USER_ID + 1
+            val signalTargetUser by collectLastValue(underTest.autoAddSignal(USER_ID))
+            val signalOtherUser by collectLastValue(underTest.autoAddSignal(otherUserId))
+            assertThat(signalTargetUser).isNull()
+            assertThat(signalOtherUser).isNull()
+
+            a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+                USER_ID,
+                setOf(TARGET_COMPONENT_FLATTEN)
+            )
+
+            assertThat(signalTargetUser).isEqualTo(AutoAddSignal.Add(SPEC))
+            assertThat(signalOtherUser).isNull()
+        }
+
+    @Test
+    fun strategyAlways() {
+        assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Always)
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create("spec")
+        private val TARGET_COMPONENT = ComponentName("FakePkgName", "FakeClassName")
+        private val TARGET_COMPONENT_FLATTEN = TARGET_COMPONENT.flattenToString()
+        private val OTHER_COMPONENT_FLATTEN =
+            ComponentName("FakePkgName", "OtherClassName").flattenToString()
+        private const val USER_ID = 0
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt
new file mode 100644
index 0000000..b4a0a37
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt
@@ -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 com.android.systemui.util.kotlin
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.kotlin.BooleanFlowOperators.and
+import com.android.systemui.util.kotlin.BooleanFlowOperators.not
+import com.android.systemui.util.kotlin.BooleanFlowOperators.or
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BooleanFlowOperatorsTest : SysuiTestCase() {
+
+    val kosmos = testKosmos()
+    val testScope = kosmos.testScope
+
+    @Test
+    fun and_allTrue_returnsTrue() =
+        testScope.runTest {
+            val result by collectLastValue(and(TRUE, TRUE))
+            assertThat(result).isTrue()
+        }
+
+    @Test
+    fun and_anyFalse_returnsFalse() =
+        testScope.runTest {
+            val result by collectLastValue(and(TRUE, FALSE, TRUE))
+            assertThat(result).isFalse()
+        }
+
+    @Test
+    fun and_allFalse_returnsFalse() =
+        testScope.runTest {
+            val result by collectLastValue(and(FALSE, FALSE, FALSE))
+            assertThat(result).isFalse()
+        }
+
+    @Test
+    fun or_allTrue_returnsTrue() =
+        testScope.runTest {
+            val result by collectLastValue(or(TRUE, TRUE))
+            assertThat(result).isTrue()
+        }
+
+    @Test
+    fun or_anyTrue_returnsTrue() =
+        testScope.runTest {
+            val result by collectLastValue(or(FALSE, TRUE, FALSE))
+            assertThat(result).isTrue()
+        }
+
+    @Test
+    fun or_allFalse_returnsFalse() =
+        testScope.runTest {
+            val result by collectLastValue(or(FALSE, FALSE, FALSE))
+            assertThat(result).isFalse()
+        }
+
+    @Test
+    fun not_true_returnsFalse() =
+        testScope.runTest {
+            val result by collectLastValue(not(TRUE))
+            assertThat(result).isFalse()
+        }
+
+    @Test
+    fun not_false_returnsFalse() =
+        testScope.runTest {
+            val result by collectLastValue(not(FALSE))
+            assertThat(result).isTrue()
+        }
+
+    private companion object {
+        val TRUE: Flow<Boolean>
+            get() = flowOf(true)
+        val FALSE: Flow<Boolean>
+            get() = flowOf(false)
+    }
+}
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 8ec5ccd..2ab0813 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -184,6 +184,7 @@
     <item type="id" name="action_move_to_edge_and_hide"/>
     <item type="id" name="action_move_out_edge_and_show"/>
     <item type="id" name="action_remove_menu"/>
+    <item type="id" name="action_edit"/>
 
     <!-- rounded corner view id -->
     <item type="id" name="rounded_corner_top_left"/>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 9bc7681..47ac96c 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1089,6 +1089,8 @@
     <string name="cta_label_to_open_widget_picker">Add more widgets</string>
     <!-- Text for the popup to be displayed after dismissing the CTA tile. [CHAR LIMIT=50] -->
     <string name="popup_on_dismiss_cta_tile_text">Long press to customize widgets</string>
+    <!-- Text for the button to configure widgets after long press. [CHAR LIMIT=50] -->
+    <string name="button_to_configure_widgets_text">Customize widgets</string>
     <!-- Label for the button which configures widgets [CHAR LIMIT=NONE] -->
     <string name="edit_widget">Edit widget</string>
     <!-- Description for the button that removes a widget on click. [CHAR LIMIT=50] -->
@@ -2558,6 +2560,8 @@
     <string name="accessibility_floating_button_action_remove_menu">Remove</string>
     <!-- Action in accessibility menu to toggle on/off the accessibility feature. [CHAR LIMIT=30]-->
     <string name="accessibility_floating_button_action_double_tap_to_toggle">toggle</string>
+    <!-- Action in accessibility menu to open the shortcut edit menu" [CHAR LIMIT=30]-->
+    <string name="accessibility_floating_button_action_edit">Edit</string>
 
     <!-- Device Controls strings -->
     <!-- Device Controls, Quick Settings tile title [CHAR LIMIT=30] -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java
index 259cca8..9e92c93 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java
@@ -16,8 +16,11 @@
 
 package com.android.systemui.shared.system;
 
-import android.graphics.Matrix;
+import static android.os.Trace.TRACE_TAG_INPUT;
+
 import android.os.Looper;
+import android.os.Trace;
+import android.util.Log;
 import android.view.BatchedInputEventReceiver;
 import android.view.Choreographer;
 import android.view.InputChannel;
@@ -52,23 +55,24 @@
         return target.addBatch(src);
     }
 
-    /** @see MotionEvent#createRotateMatrix */
-    public static Matrix createRotationMatrix(
-            /*@Surface.Rotation*/ int rotation, int displayW, int displayH) {
-        return MotionEvent.createRotateMatrix(rotation, displayW, displayH);
-    }
-
     /**
      * @see BatchedInputEventReceiver
      */
     public static class InputEventReceiver {
 
+        private final String mName;
         private final BatchedInputEventReceiver mReceiver;
 
+        @Deprecated
         public InputEventReceiver(InputChannel inputChannel, Looper looper,
                 Choreographer choreographer, final InputEventListener listener) {
-            mReceiver = new BatchedInputEventReceiver(inputChannel, looper, choreographer) {
+            this("unknown", inputChannel, looper, choreographer, listener);
+        }
 
+        public InputEventReceiver(String name, InputChannel inputChannel, Looper looper,
+                Choreographer choreographer, final InputEventListener listener) {
+            mName = name;
+            mReceiver = new BatchedInputEventReceiver(inputChannel, looper, choreographer) {
                 @Override
                 public void onInputEvent(InputEvent event) {
                     listener.onInputEvent(event);
@@ -89,6 +93,9 @@
          */
         public void dispose() {
             mReceiver.dispose();
+            Trace.instant(TRACE_TAG_INPUT, "InputMonitorCompat-" + mName + " receiver disposed");
+            Log.d(InputMonitorCompat.TAG, "Input event receiver for monitor (" + mName
+                    + ") disposed");
         }
     }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java
index c4aac11..78beaf7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java
@@ -17,8 +17,13 @@
 
 import android.hardware.input.InputManagerGlobal;
 import android.os.Looper;
+import android.os.Trace;
+import android.util.Log;
 import android.view.Choreographer;
 import android.view.InputMonitor;
+import android.view.SurfaceControl;
+
+import androidx.annotation.NonNull;
 
 import com.android.systemui.shared.system.InputChannelCompat.InputEventListener;
 import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver;
@@ -27,14 +32,20 @@
  * @see android.view.InputMonitor
  */
 public class InputMonitorCompat {
+    static final String TAG = "InputMonitorCompat";
     private final InputMonitor mInputMonitor;
+    private final String mName;
 
     /**
      * Monitor input on the specified display for gestures.
      */
-    public InputMonitorCompat(String name, int displayId) {
+    public InputMonitorCompat(@NonNull String name, int displayId) {
+        mName = name + "-disp" + displayId;
         mInputMonitor = InputManagerGlobal.getInstance()
                 .monitorGestureInput(name, displayId);
+        Trace.instant(Trace.TRACE_TAG_INPUT, "InputMonitorCompat-" + mName + " created");
+        Log.d(TAG, "Input monitor (" + mName + ") created");
+
     }
 
     /**
@@ -45,10 +56,19 @@
     }
 
     /**
+     * @see InputMonitor#getSurface()
+     */
+    public SurfaceControl getSurface() {
+        return mInputMonitor.getSurface();
+    }
+
+    /**
      * @see InputMonitor#dispose()
      */
     public void dispose() {
         mInputMonitor.dispose();
+        Trace.instant(Trace.TRACE_TAG_INPUT, "InputMonitorCompat-" + mName + " disposed");
+        Log.d(TAG, "Input monitor (" + mName + ") disposed");
     }
 
     /**
@@ -56,7 +76,9 @@
      */
     public InputEventReceiver getInputReceiver(Looper looper, Choreographer choreographer,
             InputEventListener listener) {
-        return new InputEventReceiver(mInputMonitor.getInputChannel(), looper, choreographer,
+        Trace.instant(Trace.TRACE_TAG_INPUT, "InputMonitorCompat-" + mName + " receiver created");
+        Log.d(TAG, "Input event receiver for monitor (" + mName + ") created");
+        return new InputEventReceiver(mName, mInputMonitor.getInputChannel(), looper, choreographer,
                 listener);
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index ecce223..25d7713 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -19,9 +19,9 @@
 import static android.app.StatusBarManager.SESSION_KEYGUARD;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 
+import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISSIBLE_KEYGUARD;
 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC;
 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_EXTENDED_ACCESS;
-import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISSIBLE_KEYGUARD;
 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_NONE_SECURITY;
 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_PASSWORD;
 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_SIM;
@@ -84,6 +84,7 @@
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.KeyguardWmStateRefactor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.ActivityStarter;
@@ -100,8 +101,6 @@
 import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.settings.GlobalSettings;
 
-import dagger.Lazy;
-
 import java.io.File;
 import java.util.Arrays;
 import java.util.Optional;
@@ -109,6 +108,7 @@
 import javax.inject.Inject;
 import javax.inject.Provider;
 
+import dagger.Lazy;
 import kotlinx.coroutines.Job;
 
 /** Controller for {@link KeyguardSecurityContainer} */
@@ -330,7 +330,7 @@
                 }
             }
 
-            if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            if (KeyguardWmStateRefactor.isEnabled()) {
                 mKeyguardTransitionInteractor.startDismissKeyguardTransition();
             }
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 3e8c6a7..536f3af 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -114,7 +114,6 @@
 import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.Dumpable;
-import com.android.systemui.Flags;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -383,7 +382,6 @@
     private List<SubscriptionInfo> mSubscriptionInfo;
     @VisibleForTesting
     protected int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
-    private boolean mFingerprintDetectRunning;
     private boolean mIsDreaming;
     private boolean mLogoutEnabled;
     private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -1005,7 +1003,6 @@
         final boolean wasCancellingRestarting = mFingerprintRunningState
                 == BIOMETRIC_STATE_CANCELLING_RESTARTING;
         mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
-        mFingerprintDetectRunning = false;
         if (wasCancellingRestarting) {
             KeyguardUpdateMonitor.this.updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
         } else {
@@ -1114,9 +1111,6 @@
         boolean wasRunning = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING;
         boolean isRunning = fingerprintRunningState == BIOMETRIC_STATE_RUNNING;
         mFingerprintRunningState = fingerprintRunningState;
-        if (mFingerprintRunningState == BIOMETRIC_STATE_STOPPED) {
-            mFingerprintDetectRunning = false;
-        }
         mLogger.logFingerprintRunningState(mFingerprintRunningState);
         // Clients of KeyguardUpdateMonitor don't care about the internal state about the
         // asynchronousness of the cancel cycle. So only notify them if the actually running state
@@ -2105,7 +2099,6 @@
     @VisibleForTesting
     void resetBiometricListeningState() {
         mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
-        mFingerprintDetectRunning = false;
     }
 
     @VisibleForTesting
@@ -2544,10 +2537,8 @@
             return;
         }
         final boolean shouldListenForFingerprint = shouldListenForFingerprint(isUdfpsSupported());
-        final boolean running = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING;
-        final boolean runningOrRestarting = running
+        final boolean runningOrRestarting = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING
                 || mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING;
-        final boolean runDetect = shouldRunFingerprintDetect();
         if (runningOrRestarting && !shouldListenForFingerprint) {
             if (action == BIOMETRIC_ACTION_START) {
                 mLogger.v("Ignoring stopListeningForFingerprint()");
@@ -2559,24 +2550,10 @@
                 mLogger.v("Ignoring startListeningForFingerprint()");
                 return;
             }
-            startListeningForFingerprint(runDetect);
-        } else if (running && runDetect && !mFingerprintDetectRunning) {
-            if (action == BIOMETRIC_ACTION_STOP) {
-                mLogger.v("Ignoring startListeningForFingerprint(detect)");
-                return;
-            }
-            // stop running authentication and start running fingerprint detection
-            stopListeningForFingerprint();
-            startListeningForFingerprint(true);
+            startListeningForFingerprint();
         }
     }
 
-    private boolean shouldRunFingerprintDetect() {
-        return !isUnlockingWithFingerprintAllowed()
-                || (Flags.runFingerprintDetectOnDismissibleKeyguard()
-                && getUserCanSkipBouncer(mSelectedUserInteractor.getSelectedUserId()));
-    }
-
     /**
      * If a user is encrypted or not.
      * This is NOT related to the lock screen being visible or not.
@@ -2832,6 +2809,7 @@
                         && biometricEnabledForUser
                         && !isUserInLockdown(user);
         final boolean strongerAuthRequired = !isUnlockingWithFingerprintAllowed();
+        final boolean isSideFps = isSfpsSupported() && isSfpsEnrolled();
         final boolean shouldListenBouncerState =
                 !strongerAuthRequired || !mPrimaryBouncerIsOrWillBeShowing;
 
@@ -2894,7 +2872,7 @@
         }
     }
 
-    private void startListeningForFingerprint(boolean runDetect) {
+    private void startListeningForFingerprint() {
         final int userId = mSelectedUserInteractor.getSelectedUserId();
         final boolean unlockPossible = isUnlockWithFingerprintPossible(userId);
         if (mFingerprintCancelSignal != null) {
@@ -2924,20 +2902,18 @@
                         mFingerprintInteractiveToAuthProvider.getVendorExtension(userId));
             }
 
-            if (runDetect) {
+            if (!isUnlockingWithFingerprintAllowed()) {
                 mLogger.v("startListeningForFingerprint - detect");
                 mFpm.detectFingerprint(
                         mFingerprintCancelSignal,
                         mFingerprintDetectionCallback,
                         fingerprintAuthenticateOptions);
-                mFingerprintDetectRunning = true;
             } else {
                 mLogger.v("startListeningForFingerprint");
                 mFpm.authenticate(null /* crypto */, mFingerprintCancelSignal,
                         mFingerprintAuthenticationCallback,
                         null /* handler */,
                         fingerprintAuthenticateOptions);
-                mFingerprintDetectRunning = false;
             }
             setFingerprintRunningState(BIOMETRIC_STATE_RUNNING);
         }
@@ -3962,7 +3938,6 @@
                 mSelectedUserInteractor.getSelectedUserId()));
         pw.println("  getUserUnlockedWithBiometric()="
                 + getUserUnlockedWithBiometric(mSelectedUserInteractor.getSelectedUserId()));
-        pw.println("  mFingerprintDetectRunning=" + mFingerprintDetectRunning);
         pw.println("  SIM States:");
         for (SimData data : mSimDatas.values()) {
             pw.println("    " + data.toString());
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index d5dc85c..8e98150 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -63,6 +63,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor;
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
@@ -87,6 +88,8 @@
 
 import javax.inject.Inject;
 
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
+
 /**
  * Controls when to show the LockIcon affordance (lock/unlocked icon or circle) on lock screen.
  *
@@ -717,6 +720,7 @@
         return mDownDetected;
     }
 
+    @ExperimentalCoroutinesApi
     @VisibleForTesting
     protected void onLongPress() {
         cancelTouches();
@@ -727,7 +731,8 @@
 
         // pre-emptively set to true to hide view
         mIsBouncerShowing = true;
-        if (mUdfpsSupported && mShowUnlockIcon && mAuthRippleController != null) {
+        if (!DeviceEntryUdfpsRefactor.isEnabled()
+                && mUdfpsSupported && mShowUnlockIcon && mAuthRippleController != null) {
             mAuthRippleController.showUnlockRipple(FINGERPRINT);
         }
         updateVisibility();
diff --git a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
index 0f5f869..4372826 100644
--- a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
@@ -20,11 +20,12 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.pm.UserInfo;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
 import android.os.UserHandle;
 
 import androidx.annotation.NonNull;
 
-import com.android.systemui.res.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.GuestResetOrExitSessionReceiver.ResetSessionDialogFactory;
@@ -32,6 +33,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.qs.QSUserSwitcherEvent;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -61,6 +63,7 @@
     private final SecureSettings mSecureSettings;
     private final ResetSessionDialogFactory mResetSessionDialogFactory;
     private final GuestSessionNotification mGuestSessionNotification;
+    private final HandlerThread mHandlerThread;
 
     @VisibleForTesting
     public final UserTracker.Callback mUserChangedCallback =
@@ -111,13 +114,16 @@
         mSecureSettings = secureSettings;
         mGuestSessionNotification = guestSessionNotification;
         mResetSessionDialogFactory = resetSessionDialogFactory;
+        mHandlerThread = new HandlerThread("GuestResumeSessionReceiver");
+        mHandlerThread.start();
     }
 
     /**
      * Register this receiver with the {@link BroadcastDispatcher}
      */
     public void register() {
-        mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
+        mUserTracker.addCallback(mUserChangedCallback,
+                  new HandlerExecutor(mHandlerThread.getThreadHandler()));
     }
 
     private void cancelDialog() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
index 8c2d221..35f9344 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.accessibility
 
+import com.android.systemui.accessibility.data.repository.AccessibilityQsShortcutsRepository
+import com.android.systemui.accessibility.data.repository.AccessibilityQsShortcutsRepositoryImpl
 import com.android.systemui.accessibility.data.repository.ColorCorrectionRepository
 import com.android.systemui.accessibility.data.repository.ColorCorrectionRepositoryImpl
 import com.android.systemui.accessibility.data.repository.ColorInversionRepository
@@ -31,4 +33,9 @@
 
     @Binds
     fun colorInversionRepository(impl: ColorInversionRepositoryImpl): ColorInversionRepository
+
+    @Binds
+    fun accessibilityQsShortcutsRepository(
+        impl: AccessibilityQsShortcutsRepositoryImpl
+    ): AccessibilityQsShortcutsRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepository.kt
new file mode 100644
index 0000000..401ac0f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepository.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.accessibility.data.repository
+
+import android.util.SparseArray
+import androidx.annotation.GuardedBy
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.SharedFlow
+
+/** Provides data related to accessibility quick setting shortcut option. */
+interface AccessibilityQsShortcutsRepository {
+    /**
+     * Observable for the a11y features the user chooses in the Settings app to use the quick
+     * setting option.
+     */
+    fun a11yQsShortcutTargets(userId: Int): SharedFlow<Set<String>>
+}
+
+@SysUISingleton
+class AccessibilityQsShortcutsRepositoryImpl
+@Inject
+constructor(
+    private val userA11yQsShortcutsRepositoryFactory: UserA11yQsShortcutsRepository.Factory,
+) : AccessibilityQsShortcutsRepository {
+
+    @GuardedBy("userA11yQsShortcutsRepositories")
+    private val userA11yQsShortcutsRepositories = SparseArray<UserA11yQsShortcutsRepository>()
+
+    override fun a11yQsShortcutTargets(userId: Int): SharedFlow<Set<String>> {
+        return synchronized(userA11yQsShortcutsRepositories) {
+            if (userId !in userA11yQsShortcutsRepositories) {
+                val userA11yQsShortcutsRepository =
+                    userA11yQsShortcutsRepositoryFactory.create(userId)
+                userA11yQsShortcutsRepositories.put(userId, userA11yQsShortcutsRepository)
+            }
+            userA11yQsShortcutsRepositories.get(userId).targets
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepository.kt
new file mode 100644
index 0000000..ed91f03
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepository.kt
@@ -0,0 +1,78 @@
+/*
+ * 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.accessibility.data.repository
+
+import android.provider.Settings
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
+
+/**
+ * Single user version of [AccessibilityQsShortcutsRepository]. It provides a similar interface as
+ * [TileSpecRepository], but focusing solely on the user it was created for. It observes the changes
+ * on the [Settings.Secure.ACCESSIBILITY_QS_TARGETS] for a given user
+ */
+class UserA11yQsShortcutsRepository
+@AssistedInject
+constructor(
+    @Assisted private val userId: Int,
+    private val secureSettings: SecureSettings,
+    @Application private val applicationScope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+) {
+    val targets =
+        secureSettings
+            .observerFlow(userId, SETTING_NAME)
+            // Force an update
+            .onStart { emit(Unit) }
+            .map { getA11yQsShortcutTargets(userId) }
+            .flowOn(backgroundDispatcher)
+            .shareIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                replay = 1
+            )
+
+    private fun getA11yQsShortcutTargets(userId: Int): Set<String> {
+        val settingValue = secureSettings.getStringForUser(SETTING_NAME, userId) ?: ""
+        return settingValue.split(SETTING_SEPARATOR).filterNot { it.isEmpty() }.toSet()
+    }
+
+    companion object {
+        const val SETTING_NAME = Settings.Secure.ACCESSIBILITY_QS_TARGETS
+        const val SETTING_SEPARATOR = ":"
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(
+            userId: Int,
+        ): UserA11yQsShortcutsRepository
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
index 568b24d..7fd72ec 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
@@ -16,127 +16,138 @@
 
 package com.android.systemui.accessibility.floatingmenu;
 
+import static android.R.id.empty;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
+import android.util.ArrayMap;
+import android.util.Pair;
 import android.view.MotionEvent;
 
 import androidx.annotation.NonNull;
 import androidx.dynamicanimation.animation.DynamicAnimation;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Flags;
+import com.android.wm.shell.common.bubbles.DismissCircleView;
 import com.android.wm.shell.common.bubbles.DismissView;
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
 
+import java.util.Map;
+import java.util.Objects;
+
 /**
  * Controls the interaction between {@link MagnetizedObject} and
  * {@link MagnetizedObject.MagneticTarget}.
  */
 class DragToInteractAnimationController {
-    private static final boolean ENABLE_FLING_TO_DISMISS_MENU = false;
     private static final float COMPLETELY_OPAQUE = 1.0f;
     private static final float COMPLETELY_TRANSPARENT = 0.0f;
     private static final float CIRCLE_VIEW_DEFAULT_SCALE = 1.0f;
     private static final float ANIMATING_MAX_ALPHA = 0.7f;
 
+    private final DragToInteractView mInteractView;
     private final DismissView mDismissView;
     private final MenuView mMenuView;
-    private final ValueAnimator mDismissAnimator;
-    private final MagnetizedObject<?> mMagnetizedObject;
-    private float mMinDismissSize;
+
+    /**
+     * MagnetizedObject cannot differentiate between its MagnetizedTargets,
+     * so we need an object & an animator for every interactable.
+     */
+    private final ArrayMap<Integer, Pair<MagnetizedObject<MenuView>, ValueAnimator>> mInteractMap;
+
+    private float mMinInteractSize;
     private float mSizePercent;
 
+    DragToInteractAnimationController(DragToInteractView interactView, MenuView menuView) {
+        mDismissView = null;
+        mInteractView = interactView;
+        mInteractView.setPivotX(interactView.getWidth() / 2.0f);
+        mInteractView.setPivotY(interactView.getHeight() / 2.0f);
+        mMenuView = menuView;
+
+        updateResources();
+
+        mInteractMap = new ArrayMap<>();
+        interactView.getInteractMap().forEach((viewId, pair) -> {
+            DismissCircleView circleView = pair.getFirst();
+            createMagnetizedObjectAndAnimator(circleView);
+        });
+    }
+
     DragToInteractAnimationController(DismissView dismissView, MenuView menuView) {
         mDismissView = dismissView;
+        mInteractView = null;
         mDismissView.setPivotX(dismissView.getWidth() / 2.0f);
         mDismissView.setPivotY(dismissView.getHeight() / 2.0f);
         mMenuView = menuView;
 
         updateResources();
 
-        mDismissAnimator = ValueAnimator.ofFloat(COMPLETELY_OPAQUE, COMPLETELY_TRANSPARENT);
-        mDismissAnimator.addUpdateListener(dismissAnimation -> {
-            final float animatedValue = (float) dismissAnimation.getAnimatedValue();
-            final float scaleValue = Math.max(animatedValue, mSizePercent);
-            dismissView.getCircle().setScaleX(scaleValue);
-            dismissView.getCircle().setScaleY(scaleValue);
-
-            menuView.setAlpha(Math.max(animatedValue, ANIMATING_MAX_ALPHA));
-        });
-
-        mDismissAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
-                super.onAnimationEnd(animation, isReverse);
-
-                if (isReverse) {
-                    mDismissView.getCircle().setScaleX(CIRCLE_VIEW_DEFAULT_SCALE);
-                    mDismissView.getCircle().setScaleY(CIRCLE_VIEW_DEFAULT_SCALE);
-                    mMenuView.setAlpha(COMPLETELY_OPAQUE);
-                }
-            }
-        });
-
-        mMagnetizedObject =
-                new MagnetizedObject<MenuView>(mMenuView.getContext(), mMenuView,
-                        new MenuAnimationController.MenuPositionProperty(
-                                DynamicAnimation.TRANSLATION_X),
-                        new MenuAnimationController.MenuPositionProperty(
-                                DynamicAnimation.TRANSLATION_Y)) {
-                    @Override
-                    public void getLocationOnScreen(MenuView underlyingObject, int[] loc) {
-                        underlyingObject.getLocationOnScreen(loc);
-                    }
-
-                    @Override
-                    public float getHeight(MenuView underlyingObject) {
-                        return underlyingObject.getHeight();
-                    }
-
-                    @Override
-                    public float getWidth(MenuView underlyingObject) {
-                        return underlyingObject.getWidth();
-                    }
-                };
-
-        final MagnetizedObject.MagneticTarget magneticTarget = new MagnetizedObject.MagneticTarget(
-                dismissView.getCircle(), (int) mMinDismissSize);
-        mMagnetizedObject.addTarget(magneticTarget);
-        mMagnetizedObject.setFlingToTargetEnabled(ENABLE_FLING_TO_DISMISS_MENU);
+        mInteractMap = new ArrayMap<>();
+        createMagnetizedObjectAndAnimator(dismissView.getCircle());
     }
 
-    void showDismissView(boolean show) {
-        if (show) {
-            mDismissView.show();
-        } else {
-            mDismissView.hide();
+    void showInteractView(boolean show) {
+        if (Flags.floatingMenuDragToEdit() && mInteractView != null) {
+            if (show) {
+                mInteractView.show();
+            } else {
+                mInteractView.hide();
+            }
+        } else if (mDismissView != null) {
+            if (show) {
+                mDismissView.show();
+            } else {
+                mDismissView.hide();
+            }
         }
     }
 
     void setMagnetListener(MagnetizedObject.MagnetListener magnetListener) {
-        mMagnetizedObject.setMagnetListener(magnetListener);
+        mInteractMap.forEach((viewId, pair) -> {
+            MagnetizedObject<?> magnetizedObject = pair.first;
+            magnetizedObject.setMagnetListener(magnetListener);
+        });
     }
 
     @VisibleForTesting
-    MagnetizedObject.MagnetListener getMagnetListener() {
-        return mMagnetizedObject.getMagnetListener();
+    MagnetizedObject.MagnetListener getMagnetListener(int id) {
+        return Objects.requireNonNull(mInteractMap.get(id)).first.getMagnetListener();
     }
 
     void maybeConsumeDownMotionEvent(MotionEvent event) {
-        mMagnetizedObject.maybeConsumeMotionEvent(event);
+        mInteractMap.forEach((viewId, pair) -> {
+            MagnetizedObject<?> magnetizedObject = pair.first;
+            magnetizedObject.maybeConsumeMotionEvent(event);
+        });
+    }
+
+    private int maybeConsumeMotionEvent(MotionEvent event) {
+        for (Map.Entry<Integer, Pair<MagnetizedObject<MenuView>, ValueAnimator>> set:
+                mInteractMap.entrySet()) {
+            MagnetizedObject<MenuView> magnetizedObject = set.getValue().first;
+            if (magnetizedObject.maybeConsumeMotionEvent(event)) {
+                return set.getKey();
+            }
+        }
+        return empty;
     }
 
     /**
-     * This used to pass {@link MotionEvent#ACTION_DOWN} to the magnetized object to check if it was
-     * within the magnetic field. It should be used in the {@link MenuListViewTouchHandler}.
+     * This used to pass {@link MotionEvent#ACTION_DOWN} to the magnetized objects
+     * to check if it was within a magnetic field.
+     * It should be used in the {@link MenuListViewTouchHandler}.
      *
      * @param event that move the magnetized object which is also the menu list view.
-     * @return true if the location of the motion events moves within the magnetic field of a
-     * target, but false if didn't set
+     * @return id of a target if the location of the motion events moves
+     * within the field of the target, otherwise it returns{@link android.R.id#empty}.
+     * <p>
      * {@link DragToInteractAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
      */
-    boolean maybeConsumeMoveMotionEvent(MotionEvent event) {
-        return mMagnetizedObject.maybeConsumeMotionEvent(event);
+    int maybeConsumeMoveMotionEvent(MotionEvent event) {
+        return maybeConsumeMotionEvent(event);
     }
 
     /**
@@ -144,31 +155,93 @@
      * within the magnetic field. It should be used in the {@link MenuListViewTouchHandler}.
      *
      * @param event that move the magnetized object which is also the menu list view.
-     * @return true if the location of the motion events moves within the magnetic field of a
-     * target, but false if didn't set
+     * @return id of a target if the location of the motion events moves
+     * within the field of the target, otherwise it returns{@link android.R.id#empty}.
      * {@link DragToInteractAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
      */
-    boolean maybeConsumeUpMotionEvent(MotionEvent event) {
-        return mMagnetizedObject.maybeConsumeMotionEvent(event);
+    int maybeConsumeUpMotionEvent(MotionEvent event) {
+        return maybeConsumeMotionEvent(event);
     }
 
-    void animateDismissMenu(boolean scaleUp) {
+    void animateInteractMenu(int targetViewId, boolean scaleUp) {
+        Pair<MagnetizedObject<MenuView>, ValueAnimator> value = mInteractMap.get(targetViewId);
+        if (value == null) {
+            return;
+        }
+        ValueAnimator animator = value.second;
         if (scaleUp) {
-            mDismissAnimator.start();
+            animator.start();
         } else {
-            mDismissAnimator.reverse();
+            animator.reverse();
         }
     }
 
     void updateResources() {
-        final float maxDismissSize = mDismissView.getResources().getDimensionPixelSize(
+        final float maxInteractSize = mMenuView.getResources().getDimensionPixelSize(
                 com.android.wm.shell.R.dimen.dismiss_circle_size);
-        mMinDismissSize = mDismissView.getResources().getDimensionPixelSize(
+        mMinInteractSize = mMenuView.getResources().getDimensionPixelSize(
                 com.android.wm.shell.R.dimen.dismiss_circle_small);
-        mSizePercent = mMinDismissSize / maxDismissSize;
+        mSizePercent = mMinInteractSize / maxInteractSize;
     }
 
-    interface DismissCallback {
-        void onDismiss();
+    /**
+     * Creates a magnetizedObject & valueAnimator pair for the provided circleView,
+     * and adds them to the interactMap.
+     *
+     * @param circleView circleView to create objects for.
+     */
+    private void createMagnetizedObjectAndAnimator(DismissCircleView circleView) {
+        MagnetizedObject<MenuView> magnetizedObject = new MagnetizedObject<MenuView>(
+                mMenuView.getContext(), mMenuView,
+                new MenuAnimationController.MenuPositionProperty(
+                        DynamicAnimation.TRANSLATION_X),
+                new MenuAnimationController.MenuPositionProperty(
+                        DynamicAnimation.TRANSLATION_Y)) {
+            @Override
+            public void getLocationOnScreen(MenuView underlyingObject, @NonNull int[] loc) {
+                underlyingObject.getLocationOnScreen(loc);
+            }
+
+            @Override
+            public float getHeight(MenuView underlyingObject) {
+                return underlyingObject.getHeight();
+            }
+
+            @Override
+            public float getWidth(MenuView underlyingObject) {
+                return underlyingObject.getWidth();
+            }
+        };
+        // Avoid unintended selection of an object / option
+        magnetizedObject.setFlingToTargetEnabled(false);
+        magnetizedObject.addTarget(new MagnetizedObject.MagneticTarget(
+                circleView, (int) mMinInteractSize));
+
+        final ValueAnimator animator =
+                ValueAnimator.ofFloat(COMPLETELY_OPAQUE, COMPLETELY_TRANSPARENT);
+
+        animator.addUpdateListener(dismissAnimation -> {
+            final float animatedValue = (float) dismissAnimation.getAnimatedValue();
+            final float scaleValue = Math.max(animatedValue, mSizePercent);
+            circleView.setScaleX(scaleValue);
+            circleView.setScaleY(scaleValue);
+
+            mMenuView.setAlpha(Math.max(animatedValue, ANIMATING_MAX_ALPHA));
+        });
+
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
+                super.onAnimationEnd(animation, isReverse);
+
+                if (isReverse) {
+                    circleView.setScaleX(CIRCLE_VIEW_DEFAULT_SCALE);
+                    circleView.setScaleY(CIRCLE_VIEW_DEFAULT_SCALE);
+                    mMenuView.setAlpha(COMPLETELY_OPAQUE);
+                }
+            }
+        });
+
+        mInteractMap.put(circleView.getId(), new Pair<>(magnetizedObject, animator));
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
new file mode 100644
index 0000000..0ef3d20
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
@@ -0,0 +1,322 @@
+/*
+ * 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.accessibility.floatingmenu
+
+import android.animation.ObjectAnimator
+import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.GradientDrawable
+import android.util.ArrayMap
+import android.util.IntProperty
+import android.util.Log
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowInsets
+import android.view.WindowManager
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import android.widget.Space
+import androidx.annotation.ColorRes
+import androidx.annotation.DimenRes
+import androidx.annotation.DrawableRes
+import androidx.core.content.ContextCompat
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY
+import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW
+import com.android.wm.shell.R
+import com.android.wm.shell.animation.PhysicsAnimator
+import com.android.wm.shell.common.bubbles.DismissCircleView
+import com.android.wm.shell.common.bubbles.DismissView
+
+/**
+ * View that handles interactions between DismissCircleView and BubbleStackView.
+ *
+ * @note [setup] method should be called after initialisation
+ */
+class DragToInteractView(context: Context) : FrameLayout(context) {
+    /**
+     * The configuration is used to provide module specific resource ids
+     *
+     * @see [setup] method
+     */
+    data class Config(
+        /** dimen resource id of the dismiss target circle view size */
+        @DimenRes val targetSizeResId: Int,
+        /** dimen resource id of the icon size in the dismiss target */
+        @DimenRes val iconSizeResId: Int,
+        /** dimen resource id of the bottom margin for the dismiss target */
+        @DimenRes var bottomMarginResId: Int,
+        /** dimen resource id of the height for dismiss area gradient */
+        @DimenRes val floatingGradientHeightResId: Int,
+        /** color resource id of the dismiss area gradient color */
+        @ColorRes val floatingGradientColorResId: Int,
+        /** drawable resource id of the dismiss target background */
+        @DrawableRes val backgroundResId: Int,
+        /** drawable resource id of the icon for the dismiss target */
+        @DrawableRes val iconResId: Int
+    )
+
+    companion object {
+        private const val SHOULD_SETUP = "The view isn't ready. Should be called after `setup`"
+        private val TAG = DragToInteractView::class.simpleName
+    }
+
+    // START DragToInteractView modification
+    // We could technically access each DismissCircleView from their Animator,
+    // but the animators only store a weak reference to their targets. This is safer.
+    var interactMap = ArrayMap<Int, Pair<DismissCircleView, PhysicsAnimator<DismissCircleView>>>()
+    // END DragToInteractView modification
+    var isShowing = false
+    var config: Config? = null
+
+    private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY)
+    private val INTERACT_SCRIM_FADE_MS = 200L
+    private var wm: WindowManager =
+        context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+    private var gradientDrawable: GradientDrawable? = null
+
+    private val GRADIENT_ALPHA: IntProperty<GradientDrawable> =
+        object : IntProperty<GradientDrawable>("alpha") {
+            override fun setValue(d: GradientDrawable, percent: Int) {
+                d.alpha = percent
+            }
+            override fun get(d: GradientDrawable): Int {
+                return d.alpha
+            }
+        }
+
+    init {
+        clipToPadding = false
+        clipChildren = false
+        visibility = View.INVISIBLE
+
+        // START DragToInteractView modification
+        // Resources included within implementation as we aren't concerned with decoupling them.
+        setup(
+            Config(
+                targetSizeResId = R.dimen.dismiss_circle_size,
+                iconSizeResId = R.dimen.dismiss_target_x_size,
+                bottomMarginResId = R.dimen.floating_dismiss_bottom_margin,
+                floatingGradientHeightResId = R.dimen.floating_dismiss_gradient_height,
+                floatingGradientColorResId = android.R.color.system_neutral1_900,
+                backgroundResId = R.drawable.dismiss_circle_background,
+                iconResId = R.drawable.pip_ic_close_white
+            )
+        )
+        // END DragToInteractView modification
+    }
+
+    /**
+     * Sets up view with the provided resource ids.
+     *
+     * Decouples resource dependency in order to be used externally (e.g. Launcher). Usually called
+     * with default params in module specific extension:
+     *
+     * @see [DismissView.setup] in DismissViewExt.kt
+     */
+    fun setup(config: Config) {
+        this.config = config
+
+        // Setup layout
+        layoutParams =
+            LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                resources.getDimensionPixelSize(config.floatingGradientHeightResId),
+                Gravity.BOTTOM
+            )
+        updatePadding()
+
+        // Setup gradient
+        gradientDrawable = createGradient(color = config.floatingGradientColorResId)
+        background = gradientDrawable
+
+        // START DragToInteractView modification
+
+        // Setup LinearLayout. Added to organize multiple circles.
+        val linearLayout = LinearLayout(context)
+        linearLayout.layoutParams =
+            LinearLayout.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT
+            )
+        linearLayout.weightSum = 0f
+        addView(linearLayout)
+
+        // Setup DismissCircleView. Code block replaced with repeatable functions
+        addSpace(linearLayout)
+        addCircle(
+            config,
+            com.android.systemui.res.R.id.action_remove_menu,
+            R.drawable.pip_ic_close_white,
+            linearLayout
+        )
+        addCircle(
+            config,
+            com.android.systemui.res.R.id.action_edit,
+            com.android.systemui.res.R.drawable.ic_screenshot_edit,
+            linearLayout
+        )
+        // END DragToInteractView modification
+    }
+
+    /** Animates this view in. */
+    fun show() {
+        if (isShowing) return
+        val gradientDrawable = checkExists(gradientDrawable) ?: return
+        isShowing = true
+        visibility = View.VISIBLE
+        val alphaAnim =
+            ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA, gradientDrawable.alpha, 255)
+        alphaAnim.duration = INTERACT_SCRIM_FADE_MS
+        alphaAnim.start()
+
+        // START DragToInteractView modification
+        interactMap.forEach {
+            val animator = it.value.second
+            animator.cancel()
+            animator.spring(DynamicAnimation.TRANSLATION_Y, 0f, spring).start()
+        }
+        // END DragToInteractView modification
+    }
+
+    /**
+     * Animates this view out, as well as the circle that encircles the bubbles, if they were
+     * dragged into the target and encircled.
+     */
+    fun hide() {
+        if (!isShowing) return
+        val gradientDrawable = checkExists(gradientDrawable) ?: return
+        isShowing = false
+        val alphaAnim =
+            ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA, gradientDrawable.alpha, 0)
+        alphaAnim.duration = INTERACT_SCRIM_FADE_MS
+        alphaAnim.start()
+
+        // START DragToInteractView modification
+        interactMap.forEach {
+            val animator = it.value.second
+            animator
+                .spring(DynamicAnimation.TRANSLATION_Y, height.toFloat(), spring)
+                .withEndActions({ visibility = View.INVISIBLE })
+                .start()
+        }
+        // END DragToInteractView modification
+    }
+
+    /** Cancels the animator for the dismiss target. */
+    fun cancelAnimators() {
+        // START DragToInteractView modification
+        interactMap.forEach {
+            val animator = it.value.second
+            animator.cancel()
+        }
+        // END DragToInteractView modification
+    }
+
+    fun updateResources() {
+        val config = checkExists(config) ?: return
+        updatePadding()
+        layoutParams.height = resources.getDimensionPixelSize(config.floatingGradientHeightResId)
+        val targetSize = resources.getDimensionPixelSize(config.targetSizeResId)
+
+        // START DragToInteractView modification
+        interactMap.forEach {
+            val circle = it.value.first
+            circle.layoutParams.width = targetSize
+            circle.layoutParams.height = targetSize
+            circle.requestLayout()
+        }
+        // END DragToInteractView modification
+    }
+
+    private fun createGradient(@ColorRes color: Int): GradientDrawable {
+        val gradientColor = ContextCompat.getColor(context, color)
+        val alpha = 0.7f * 255
+        val gradientColorWithAlpha =
+            Color.argb(
+                alpha.toInt(),
+                Color.red(gradientColor),
+                Color.green(gradientColor),
+                Color.blue(gradientColor)
+            )
+        val gd =
+            GradientDrawable(
+                GradientDrawable.Orientation.BOTTOM_TOP,
+                intArrayOf(gradientColorWithAlpha, Color.TRANSPARENT)
+            )
+        gd.setDither(true)
+        gd.alpha = 0
+        return gd
+    }
+
+    private fun updatePadding() {
+        val config = checkExists(config) ?: return
+        val insets: WindowInsets = wm.currentWindowMetrics.windowInsets
+        val navInset = insets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars())
+        setPadding(
+            0,
+            0,
+            0,
+            navInset.bottom + resources.getDimensionPixelSize(config.bottomMarginResId)
+        )
+    }
+
+    /**
+     * Checks if the value is set up and exists, if not logs an exception. Used for convenient
+     * logging in case `setup` wasn't called before
+     *
+     * @return value provided as argument
+     */
+    private fun <T> checkExists(value: T?): T? {
+        if (value == null) Log.e(TAG, SHOULD_SETUP)
+        return value
+    }
+
+    // START DragToInteractView modification
+    private fun addSpace(parent: LinearLayout) {
+        val space = Space(context)
+        // Spaces are weighted equally to space out circles evenly
+        space.layoutParams =
+            LinearLayout.LayoutParams(
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                1f
+            )
+        parent.addView(space)
+        parent.weightSum = parent.weightSum + 1f
+    }
+
+    private fun addCircle(config: Config, id: Int, iconResId: Int, parent: LinearLayout) {
+        val targetSize = resources.getDimensionPixelSize(config.targetSizeResId)
+        val circleLayoutParams = LinearLayout.LayoutParams(targetSize, targetSize, 0f)
+        circleLayoutParams.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
+        val circle = DismissCircleView(context)
+        circle.id = id
+        circle.setup(config.backgroundResId, iconResId, config.iconSizeResId)
+        circle.layoutParams = circleLayoutParams
+
+        // Initial position with circle offscreen so it's animated up
+        circle.translationY =
+            resources.getDimensionPixelSize(config.floatingGradientHeightResId).toFloat()
+
+        interactMap[circle.id] = Pair(circle, PhysicsAnimator.getInstance(circle))
+        parent.addView(circle)
+        addSpace(parent)
+    }
+    // END DragToInteractView modification
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
index a270558..d3e85e0 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -37,7 +37,6 @@
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
 import com.android.systemui.Flags;
 
 import java.util.HashMap;
@@ -73,7 +72,6 @@
     private final ValueAnimator mFadeOutAnimator;
     private final Handler mHandler;
     private boolean mIsFadeEffectEnabled;
-    private DragToInteractAnimationController.DismissCallback mDismissCallback;
     private Runnable mSpringAnimationsEndAction;
 
     // Cache the animations state of {@link DynamicAnimation.TRANSLATION_X} and {@link
@@ -170,11 +168,6 @@
         mSpringAnimationsEndAction = runnable;
     }
 
-    void setDismissCallback(
-            DragToInteractAnimationController.DismissCallback dismissCallback) {
-        mDismissCallback = dismissCallback;
-    }
-
     void moveToTopLeftPosition() {
         mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false);
         final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
@@ -205,13 +198,6 @@
         constrainPositionAndUpdate(position, /* writeToPosition = */ true);
     }
 
-    void removeMenu() {
-        Preconditions.checkArgument(mDismissCallback != null,
-                "The dismiss callback should be initialized first.");
-
-        mDismissCallback.onDismiss();
-    }
-
     void flingMenuThenSpringToEdge(float x, float velocityX, float velocityY) {
         final boolean shouldMenuFlingLeft = isOnLeftSide()
                 ? velocityX < ESCAPE_VELOCITY
@@ -334,8 +320,6 @@
             moveToEdgeAndHide();
             return true;
         }
-
-        fadeOutIfEnabled();
         return false;
     }
 
@@ -453,8 +437,6 @@
         mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y);
         constrainPositionAndUpdate(position, writeToPosition);
 
-        fadeOutIfEnabled();
-
         if (mSpringAnimationsEndAction != null) {
             mSpringAnimationsEndAction.run();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java
index 9c22a77..975a602 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java
@@ -27,6 +27,7 @@
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
 import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
 
+import com.android.systemui.Flags;
 import com.android.systemui.res.R;
 
 /**
@@ -35,15 +36,18 @@
  */
 class MenuItemAccessibilityDelegate extends RecyclerViewAccessibilityDelegate.ItemDelegate {
     private final MenuAnimationController mAnimationController;
+    private final MenuViewLayer mMenuViewLayer;
 
     MenuItemAccessibilityDelegate(@NonNull RecyclerViewAccessibilityDelegate recyclerViewDelegate,
-            MenuAnimationController animationController) {
+            MenuAnimationController animationController, MenuViewLayer menuViewLayer) {
         super(recyclerViewDelegate);
         mAnimationController = animationController;
+        mMenuViewLayer = menuViewLayer;
     }
 
     @Override
-    public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
+    public void onInitializeAccessibilityNodeInfo(
+            @NonNull View host, @NonNull AccessibilityNodeInfoCompat info) {
         super.onInitializeAccessibilityNodeInfo(host, info);
 
         final Resources res = host.getResources();
@@ -90,6 +94,15 @@
                         R.id.action_remove_menu,
                         res.getString(R.string.accessibility_floating_button_action_remove_menu));
         info.addAction(removeMenu);
+
+        if (Flags.floatingMenuDragToEdit()) {
+            final AccessibilityNodeInfoCompat.AccessibilityActionCompat edit =
+                    new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+                            R.id.action_edit,
+                            res.getString(
+                                    R.string.accessibility_floating_button_action_remove_menu));
+            info.addAction(edit);
+        }
     }
 
     @Override
@@ -132,8 +145,8 @@
             return true;
         }
 
-        if (action == R.id.action_remove_menu) {
-            mAnimationController.removeMenu();
+        if (action == R.id.action_remove_menu || action == R.id.action_edit) {
+            mMenuViewLayer.dispatchAccessibilityAction(action);
             return true;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
index 52e7b91..7519168 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.accessibility.floatingmenu;
 
+import static android.R.id.empty;
+
 import android.graphics.PointF;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
@@ -78,10 +80,9 @@
                         mMenuAnimationController.onDraggingStart();
                     }
 
-                    mDragToInteractAnimationController.showDismissView(/* show= */ true);
-
-                    if (!mDragToInteractAnimationController.maybeConsumeMoveMotionEvent(
-                            motionEvent)) {
+                    mDragToInteractAnimationController.showInteractView(/* show= */ true);
+                    if (mDragToInteractAnimationController.maybeConsumeMoveMotionEvent(motionEvent)
+                            == empty) {
                         mMenuAnimationController.moveToPositionX(mMenuTranslationDown.x + dx);
                         mMenuAnimationController.moveToPositionYIfNeeded(
                                 mMenuTranslationDown.y + dy);
@@ -94,21 +95,19 @@
                     final float endX = mMenuTranslationDown.x + dx;
                     mIsDragging = false;
 
-                    if (mMenuAnimationController.maybeMoveToEdgeAndHide(endX)) {
-                        mDragToInteractAnimationController.showDismissView(/* show= */ false);
-                        mMenuAnimationController.fadeOutIfEnabled();
+                    mDragToInteractAnimationController.showInteractView(/* show= */ false);
 
+                    if (mMenuAnimationController.maybeMoveToEdgeAndHide(endX)) {
+                        mMenuAnimationController.fadeOutIfEnabled();
                         return true;
                     }
 
-                    if (!mDragToInteractAnimationController.maybeConsumeUpMotionEvent(
-                            motionEvent)) {
+                    if (mDragToInteractAnimationController.maybeConsumeUpMotionEvent(motionEvent)
+                            == empty) {
                         mVelocityTracker.computeCurrentVelocity(VELOCITY_UNIT_SECONDS);
                         mMenuAnimationController.flingMenuThenSpringToEdge(endX,
                                 mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
-                        mDragToInteractAnimationController.showDismissView(/* show= */ false);
                     }
-
                     // Avoid triggering the listener of the item.
                     return true;
                 }
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 76808cb..334cc87 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
@@ -21,24 +21,28 @@
 import android.annotation.SuppressLint;
 import android.content.ComponentCallbacks;
 import android.content.Context;
+import android.content.Intent;
 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;
 
 import androidx.annotation.NonNull;
-import androidx.core.view.AccessibilityDelegateCompat;
 import androidx.lifecycle.Observer;
 import androidx.recyclerview.widget.DiffUtil;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
-import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
 
 import com.android.internal.accessibility.dialog.AccessibilityTarget;
 import com.android.systemui.Flags;
+import com.android.systemui.util.settings.SecureSettings;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -72,26 +76,20 @@
     private final MenuAnimationController mMenuAnimationController;
     private OnTargetFeaturesChangeListener mFeaturesChangeListener;
     private OnMoveToTuckedListener mMoveToTuckedListener;
+    private SecureSettings mSecureSettings;
 
-    MenuView(Context context, MenuViewModel menuViewModel, MenuViewAppearance menuViewAppearance) {
+    MenuView(Context context, MenuViewModel menuViewModel, MenuViewAppearance menuViewAppearance,
+            SecureSettings secureSettings) {
         super(context);
 
         mMenuViewModel = menuViewModel;
         mMenuViewAppearance = menuViewAppearance;
+        mSecureSettings = secureSettings;
         mMenuAnimationController = new MenuAnimationController(this, menuViewAppearance);
         mAdapter = new AccessibilityTargetAdapter(mTargetFeatures);
         mTargetFeaturesView = new RecyclerView(context);
         mTargetFeaturesView.setAdapter(mAdapter);
         mTargetFeaturesView.setLayoutManager(new LinearLayoutManager(context));
-        mTargetFeaturesView.setAccessibilityDelegateCompat(
-                new RecyclerViewAccessibilityDelegate(mTargetFeaturesView) {
-                    @NonNull
-                    @Override
-                    public AccessibilityDelegateCompat getItemDelegate() {
-                        return new MenuItemAccessibilityDelegate(/* recyclerViewDelegate= */ this,
-                                mMenuAnimationController);
-                    }
-                });
         setLayoutParams(new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
         // Avoid drawing out of bounds of the parent view
         setClipToOutline(true);
@@ -278,6 +276,7 @@
         if (mFeaturesChangeListener != null) {
             mFeaturesChangeListener.onChange(newTargetFeatures);
         }
+
         mMenuAnimationController.fadeOutIfEnabled();
     }
 
@@ -306,6 +305,10 @@
         return mMenuViewAppearance.getMenuPosition();
     }
 
+    RecyclerView getTargetFeaturesView() {
+        return mTargetFeaturesView;
+    }
+
     void persistPositionAndUpdateEdge(Position percentagePosition) {
         mMenuViewModel.updateMenuSavingPosition(percentagePosition);
         mMenuViewAppearance.setPercentagePosition(percentagePosition);
@@ -424,6 +427,35 @@
         onPositionChanged();
     }
 
+    void gotoEditScreen() {
+        if (!Flags.floatingMenuDragToEdit()) {
+            return;
+        }
+        mMenuAnimationController.flingMenuThenSpringToEdge(
+                getMenuPosition().x, 100f, 0f);
+        mContext.startActivity(getIntentForEditScreen());
+    }
+
+    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);
+        // TODO: b/318748373 - The fragment should set its own title using the targets
+        args.putString(
+                ":settings:show_fragment_title", "Accessibility Shortcut");
+        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 97999cc..bb5364d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -59,7 +59,10 @@
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
+import androidx.core.view.AccessibilityDelegateCompat;
 import androidx.lifecycle.Observer;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
 
 import com.android.internal.accessibility.dialog.AccessibilityTarget;
 import com.android.internal.annotations.VisibleForTesting;
@@ -94,6 +97,8 @@
     private final MenuListViewTouchHandler mMenuListViewTouchHandler;
     private final MenuMessageView mMessageView;
     private final DismissView mDismissView;
+    private final DragToInteractView mDragToInteractView;
+
     private final MenuViewAppearance mMenuViewAppearance;
     private final MenuAnimationController mMenuAnimationController;
     private final AccessibilityManager mAccessibilityManager;
@@ -178,7 +183,10 @@
     };
 
     MenuViewLayer(@NonNull Context context, WindowManager windowManager,
-            AccessibilityManager accessibilityManager, IAccessibilityFloatingMenu floatingMenu,
+            AccessibilityManager accessibilityManager,
+            MenuViewModel menuViewModel,
+            MenuViewAppearance menuViewAppearance, MenuView menuView,
+            IAccessibilityFloatingMenu floatingMenu,
             SecureSettings secureSettings) {
         super(context);
 
@@ -190,43 +198,52 @@
         mFloatingMenu = floatingMenu;
         mSecureSettings = secureSettings;
 
-        mMenuViewModel = new MenuViewModel(context, accessibilityManager, secureSettings);
-        mMenuViewAppearance = new MenuViewAppearance(context, windowManager);
-        mMenuView = new MenuView(context, mMenuViewModel, mMenuViewAppearance);
+        mMenuViewModel = menuViewModel;
+        mMenuViewAppearance = menuViewAppearance;
+        mMenuView = menuView;
+        RecyclerView targetFeaturesView = mMenuView.getTargetFeaturesView();
+        targetFeaturesView.setAccessibilityDelegateCompat(
+                new RecyclerViewAccessibilityDelegate(targetFeaturesView) {
+                    @NonNull
+                    @Override
+                    public AccessibilityDelegateCompat getItemDelegate() {
+                        return new MenuItemAccessibilityDelegate(/* recyclerViewDelegate= */ this,
+                                mMenuAnimationController, MenuViewLayer.this);
+                    }
+                });
         mMenuAnimationController = mMenuView.getMenuAnimationController();
-        if (Flags.floatingMenuDragToHide()) {
-            mMenuAnimationController.setDismissCallback(this::hideMenuAndShowNotification);
-        } else {
-            mMenuAnimationController.setDismissCallback(this::hideMenuAndShowMessage);
-        }
         mMenuAnimationController.setSpringAnimationsEndAction(this::onSpringAnimationsEndAction);
         mDismissView = new DismissView(context);
+        mDragToInteractView = new DragToInteractView(context);
         DismissViewUtils.setup(mDismissView);
+        mDismissView.getCircle().setId(R.id.action_remove_menu);
         mNotificationFactory = new MenuNotificationFactory(context);
         mNotificationManager = context.getSystemService(NotificationManager.class);
-        mDragToInteractAnimationController = new DragToInteractAnimationController(
-                mDismissView, mMenuView);
+
+        if (Flags.floatingMenuDragToEdit()) {
+            mDragToInteractAnimationController = new DragToInteractAnimationController(
+                    mDragToInteractView, mMenuView);
+        } else {
+            mDragToInteractAnimationController = new DragToInteractAnimationController(
+                    mDismissView, mMenuView);
+        }
         mDragToInteractAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
             @Override
             public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
-                mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ true);
+                mDragToInteractAnimationController.animateInteractMenu(
+                        target.getTargetView().getId(), /* scaleUp= */ true);
             }
 
             @Override
             public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
                     float velocityX, float velocityY, boolean wasFlungOut) {
-                mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ false);
+                mDragToInteractAnimationController.animateInteractMenu(
+                        target.getTargetView().getId(), /* scaleUp= */ false);
             }
 
             @Override
             public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
-                if (Flags.floatingMenuDragToHide()) {
-                    hideMenuAndShowNotification();
-                } else {
-                    hideMenuAndShowMessage();
-                }
-                mDismissView.hide();
-                mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ false);
+                dispatchAccessibilityAction(target.getTargetView().getId());
             }
         });
 
@@ -262,7 +279,11 @@
         });
 
         addView(mMenuView, LayerIndex.MENU_VIEW);
-        addView(mDismissView, LayerIndex.DISMISS_VIEW);
+        if (Flags.floatingMenuDragToEdit()) {
+            addView(mDragToInteractView, LayerIndex.DISMISS_VIEW);
+        } else {
+            addView(mDismissView, LayerIndex.DISMISS_VIEW);
+        }
         addView(mMessageView, LayerIndex.MESSAGE_VIEW);
 
         if (Flags.floatingMenuAnimatedTuck()) {
@@ -272,6 +293,7 @@
 
     @Override
     public void onConfigurationChanged(@NonNull Configuration newConfig) {
+        mDragToInteractView.updateResources();
         mDismissView.updateResources();
         mDragToInteractAnimationController.updateResources();
     }
@@ -428,6 +450,23 @@
         }
     }
 
+    void dispatchAccessibilityAction(int id) {
+        if (id == R.id.action_remove_menu) {
+            if (Flags.floatingMenuDragToHide()) {
+                hideMenuAndShowNotification();
+            } else {
+                hideMenuAndShowMessage();
+            }
+        } else if (id == R.id.action_edit
+                && Flags.floatingMenuDragToEdit()) {
+            mMenuView.gotoEditScreen();
+        }
+        mDismissView.hide();
+        mDragToInteractView.hide();
+        mDragToInteractAnimationController.animateInteractMenu(
+                id, /* scaleUp= */ false);
+    }
+
     private CharSequence getMigrationMessage() {
         final Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -475,7 +514,8 @@
         mEduTooltipView = Optional.empty();
     }
 
-    private void hideMenuAndShowMessage() {
+    @VisibleForTesting
+    void hideMenuAndShowMessage() {
         final int delayTime = mAccessibilityManager.getRecommendedTimeoutMillis(
                 SHOW_MESSAGE_DELAY_MS,
                 AccessibilityManager.FLAG_CONTENT_TEXT
@@ -485,7 +525,8 @@
         mMenuAnimationController.startShrinkAnimation(() -> mMenuView.setVisibility(GONE));
     }
 
-    private void hideMenuAndShowNotification() {
+    @VisibleForTesting
+    void hideMenuAndShowNotification() {
         mMenuAnimationController.startShrinkAnimation(() -> mMenuView.setVisibility(GONE));
         showNotification();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
index 1f54952..bc9d1ff 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
@@ -39,7 +39,16 @@
     MenuViewLayerController(Context context, WindowManager windowManager,
             AccessibilityManager accessibilityManager, SecureSettings secureSettings) {
         mWindowManager = windowManager;
-        mMenuViewLayer = new MenuViewLayer(context, windowManager, accessibilityManager, this,
+
+        MenuViewModel menuViewModel = new MenuViewModel(
+                context, accessibilityManager, secureSettings);
+        MenuViewAppearance menuViewAppearance = new MenuViewAppearance(context, windowManager);
+
+        mMenuViewLayer = new MenuViewLayer(context, windowManager, accessibilityManager,
+                menuViewModel,
+                menuViewAppearance,
+                new MenuView(context, menuViewModel, menuViewAppearance, secureSettings),
+                this,
                 secureSettings);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 86f372a..d2c6227 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -25,6 +25,7 @@
 import android.hardware.biometrics.BiometricSourceType
 import android.util.DisplayMetrics
 import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.repeatOnLifecycle
 import com.android.app.animation.Interpolators
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
@@ -35,8 +36,11 @@
 import com.android.systemui.biometrics.data.repository.FacePropertyRepository
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.domain.interactor.AuthRippleInteractor
 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.res.R
@@ -51,7 +55,6 @@
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.ViewController
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import java.io.PrintWriter
 import javax.inject.Inject
 import javax.inject.Provider
@@ -64,7 +67,6 @@
  *
  * The ripple uses the accent color of the current theme.
  */
-@ExperimentalCoroutinesApi
 @SysUISingleton
 class AuthRippleController @Inject constructor(
     private val sysuiContext: Context,
@@ -81,6 +83,7 @@
     private val logger: KeyguardLogger,
     private val biometricUnlockController: BiometricUnlockController,
     private val lightRevealScrim: LightRevealScrim,
+    private val authRippleInteractor: AuthRippleInteractor,
     private val facePropertyRepository: FacePropertyRepository,
     rippleView: AuthRippleView?
 ) :
@@ -103,6 +106,22 @@
         init()
     }
 
+    init {
+        if (DeviceEntryUdfpsRefactor.isEnabled) {
+            rippleView?.repeatWhenAttached {
+                repeatOnLifecycle(androidx.lifecycle.Lifecycle.State.CREATED) {
+                    authRippleInteractor.showUnlockRipple.collect { biometricUnlockSource ->
+                        if (biometricUnlockSource == BiometricUnlockSource.FINGERPRINT_SENSOR) {
+                            showUnlockRippleInternal(BiometricSourceType.FINGERPRINT)
+                        } else {
+                            showUnlockRippleInternal(BiometricSourceType.FACE)
+                        }
+                    }
+                }
+            }
+        }
+    }
+
     @VisibleForTesting
     public override fun onViewAttached() {
         authController.addCallback(authControllerCallback)
@@ -114,7 +133,9 @@
         keyguardStateController.addCallback(this)
         wakefulnessLifecycle.addObserver(this)
         commandRegistry.registerCommand("auth-ripple") { AuthRippleCommand() }
-        biometricUnlockController.addListener(biometricModeListener)
+        if (!DeviceEntryUdfpsRefactor.isEnabled) {
+            biometricUnlockController.addListener(biometricModeListener)
+        }
     }
 
     private val biometricModeListener =
@@ -122,8 +143,9 @@
             override fun onBiometricUnlockedWithKeyguardDismissal(
                     biometricSourceType: BiometricSourceType?
             ) {
+                DeviceEntryUdfpsRefactor.assertInLegacyMode()
                 if (biometricSourceType != null) {
-                    showUnlockRipple(biometricSourceType)
+                    showUnlockRippleInternal(biometricSourceType)
                 } else {
                     logger.log(TAG,
                             LogLevel.ERROR,
@@ -146,7 +168,13 @@
         notificationShadeWindowController.setForcePluginOpen(false, this)
     }
 
-    fun showUnlockRipple(biometricSourceType: BiometricSourceType) {
+     @Deprecated("Update authRippleInteractor.showUnlockRipple instead of calling this.")
+     fun showUnlockRipple(biometricSourceType: BiometricSourceType) {
+         DeviceEntryUdfpsRefactor.assertInLegacyMode()
+         showUnlockRippleInternal(biometricSourceType)
+     }
+
+    private fun showUnlockRippleInternal(biometricSourceType: BiometricSourceType) {
         val keyguardNotShowing = !keyguardStateController.isShowing
         val unlockNotAllowed = !keyguardUpdateMonitor
                 .isUnlockingWithBiometricAllowed(biometricSourceType)
@@ -316,18 +344,6 @@
                 mView.fadeDwellRipple()
             }
         }
-
-        override fun onBiometricDetected(
-                userId: Int,
-                biometricSourceType: BiometricSourceType,
-                isStrongBiometric: Boolean
-        ) {
-            // TODO (b/309804148): add support detect auth ripple for deviceEntryUdfpsRefactor
-            if (!DeviceEntryUdfpsRefactor.isEnabled &&
-                    keyguardUpdateMonitor.getUserCanSkipBouncer(userId)) {
-                showUnlockRipple(biometricSourceType)
-            }
-        }
     }
 
     private val configurationChangedListener =
@@ -392,12 +408,12 @@
                     }
                     "fingerprint" -> {
                         pw.println("fingerprint ripple sensorLocation=$fingerprintSensorLocation")
-                        showUnlockRipple(BiometricSourceType.FINGERPRINT)
+                        showUnlockRippleInternal(BiometricSourceType.FINGERPRINT)
                     }
                     "face" -> {
                         // note: only shows when about to proceed to the home screen
                         pw.println("face ripple sensorLocation=$faceSensorLocation")
-                        showUnlockRipple(BiometricSourceType.FACE)
+                        showUnlockRippleInternal(BiometricSourceType.FACE)
                     }
                     "custom" -> {
                         if (args.size != 3 ||
@@ -424,7 +440,7 @@
             pw.println("  custom <x-location: int> <y-location: int>")
         }
 
-        fun invalidCommand(pw: PrintWriter) {
+        private fun invalidCommand(pw: PrintWriter) {
             pw.println("invalid command")
             help(pw)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
index ad2136a..d28dbc0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
@@ -94,6 +94,10 @@
                         override fun onAuthenticationStopped() {
                             updateFingerprintAuthenticateReason(AuthenticationReason.NotRunning)
                         }
+
+                        override fun onAuthenticationSucceeded(requestReason: Int, userId: Int) {}
+
+                        override fun onAuthenticationFailed(requestReason: Int, userId: Int) {}
                     }
 
                 updateFingerprintAuthenticateReason(AuthenticationReason.NotRunning)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
index 16e7f05..96582cb 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
@@ -114,7 +114,7 @@
 private fun PromptContentItem.doesExceedMaxLinesIfTwoColumn(
     resources: Resources,
 ): Boolean {
-    val passedInText: CharSequence =
+    val passedInText: String =
         when (this) {
             is PromptContentItemPlainText -> text
             is PromptContentItemBulletedText -> text
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
index c36e0e2..80d37b4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
@@ -121,11 +121,13 @@
             if (it.isAttachedToWindow) {
                 lottie = it.requireViewById<LottieAnimationView>(R.id.sidefps_animation)
                 lottie?.pauseAnimation()
+                lottie?.removeAllLottieOnCompositionLoadedListener()
                 windowManager.get().removeView(it)
             }
         }
 
         overlayView = layoutInflater.get().inflate(R.layout.sidefps_view, null, false)
+
         val overlayViewModel =
             SideFpsOverlayViewModel(
                 applicationContext,
@@ -163,8 +165,10 @@
 
                 val lottie = it.requireViewById<LottieAnimationView>(R.id.sidefps_animation)
                 lottie.addLottieOnCompositionLoadedListener { composition: LottieComposition ->
-                    viewModel.setLottieBounds(composition.bounds)
-                    overlayView.visibility = View.VISIBLE
+                    if (overlayView.visibility != View.VISIBLE) {
+                        viewModel.setLottieBounds(composition.bounds)
+                        overlayView.visibility = View.VISIBLE
+                    }
                 }
                 it.alpha = 0f
                 val overlayShowAnimator =
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 e78a7a9..0f1340a 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
@@ -259,7 +259,9 @@
 
     /** Custom content view for the prompt. */
     val contentView: Flow<PromptContentView?> =
-        promptSelectorInteractor.prompt.map { it?.contentView }.distinctUntilChanged()
+        promptSelectorInteractor.prompt
+            .map { if (customBiometricPrompt()) it?.contentView else null }
+            .distinctUntilChanged()
 
     private val originalDescription =
         promptSelectorInteractor.prompt.map { it?.description ?: "" }.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index 3287ed4..f36547b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -27,21 +27,17 @@
 import com.android.systemui.communal.widgets.CommunalAppWidgetHost
 import com.android.systemui.communal.widgets.WidgetConfigurator
 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.LogBuffer
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.util.kotlin.getValue
 import java.util.Optional
 import javax.inject.Inject
 import kotlin.coroutines.cancellation.CancellationException
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.launch
@@ -67,18 +63,15 @@
      * @param widgetIdToPriorityMap mapping of the widget ids to the priority of the widget.
      */
     fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {}
-
-    /** Update whether the app widget host should be active. */
-    fun updateAppWidgetHostActive(active: Boolean)
 }
 
 @SysUISingleton
 class CommunalWidgetRepositoryImpl
 @Inject
 constructor(
-    private val appWidgetManager: Optional<AppWidgetManager>,
+    appWidgetManagerOptional: Optional<AppWidgetManager>,
     private val appWidgetHost: CommunalAppWidgetHost,
-    @Application private val applicationScope: CoroutineScope,
+    @Background private val bgScope: CoroutineScope,
     @Background private val bgDispatcher: CoroutineDispatcher,
     private val communalWidgetHost: CommunalWidgetHost,
     private val communalWidgetDao: CommunalWidgetDao,
@@ -90,41 +83,22 @@
 
     private val logger = Logger(logBuffer, TAG)
 
-    override fun updateAppWidgetHostActive(active: Boolean) {
-        if (active == isHostActive.value) {
-            return
-        }
+    private val appWidgetManager by appWidgetManagerOptional
 
-        if (active) {
-            appWidgetHost.startListening()
-        } else {
-            appWidgetHost.stopListening()
-        }
-        isHostActive.value = active
-    }
-
-    private val isHostActive = MutableStateFlow(false)
-
-    @OptIn(ExperimentalCoroutinesApi::class)
     override val communalWidgets: Flow<List<CommunalWidgetContentModel>> =
-        isHostActive.flatMapLatest { isHostActive ->
-            if (!isHostActive || !appWidgetManager.isPresent) {
-                return@flatMapLatest flowOf(emptyList())
-            }
-            communalWidgetDao
-                .getWidgets()
-                .map { it.map(::mapToContentModel) }
-                // As this reads from a database and triggers IPCs to AppWidgetManager,
-                // it should be executed in the background.
-                .flowOn(bgDispatcher)
-        }
+        communalWidgetDao
+            .getWidgets()
+            .map { it.mapNotNull(::mapToContentModel) }
+            // As this reads from a database and triggers IPCs to AppWidgetManager,
+            // it should be executed in the background.
+            .flowOn(bgDispatcher)
 
     override fun addWidget(
         provider: ComponentName,
         priority: Int,
         configurator: WidgetConfigurator?
     ) {
-        applicationScope.launch(bgDispatcher) {
+        bgScope.launch {
             val id = communalWidgetHost.allocateIdAndBindWidget(provider)
             if (id == null) {
                 logger.e("Failed to allocate widget id to ${provider.flattenToString()}")
@@ -170,7 +144,7 @@
     }
 
     override fun deleteWidget(widgetId: Int) {
-        applicationScope.launch(bgDispatcher) {
+        bgScope.launch {
             communalWidgetDao.deleteWidgetById(widgetId)
             appWidgetHost.deleteAppWidgetId(widgetId)
             logger.i("Deleted widget with id $widgetId.")
@@ -178,7 +152,7 @@
     }
 
     override fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {
-        applicationScope.launch(bgDispatcher) {
+        bgScope.launch {
             communalWidgetDao.updateWidgetOrder(widgetIdToPriorityMap)
             logger.i({ "Updated the order of widget list with ids: $str1." }) {
                 str1 = widgetIdToPriorityMap.toString()
@@ -189,11 +163,12 @@
     @WorkerThread
     private fun mapToContentModel(
         entry: Map.Entry<CommunalItemRank, CommunalWidgetItem>
-    ): CommunalWidgetContentModel {
+    ): CommunalWidgetContentModel? {
         val (_, widgetId) = entry.value
+        val providerInfo = appWidgetManager?.getAppWidgetInfo(widgetId) ?: return null
         return CommunalWidgetContentModel(
             appWidgetId = widgetId,
-            providerInfo = appWidgetManager.get().getAppWidgetInfo(widgetId),
+            providerInfo = providerInfo,
             priority = entry.key.rank,
         )
     }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index c36f7fa..28adb77 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -37,18 +37,22 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.smartspace.data.repository.SmartspaceRepository
 import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.kotlin.BooleanFlowOperators.and
+import com.android.systemui.util.kotlin.BooleanFlowOperators.not
+import com.android.systemui.util.kotlin.BooleanFlowOperators.or
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 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.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
 
 /** Encapsulates business-logic related to communal mode. */
@@ -68,6 +72,11 @@
     private val appWidgetHost: CommunalAppWidgetHost,
     private val editWidgetsActivityStarter: EditWidgetsActivityStarter
 ) {
+    private val _editModeOpen = MutableStateFlow(false)
+
+    /** Whether edit mode is currently open. */
+    val editModeOpen: StateFlow<Boolean> = _editModeOpen.asStateFlow()
+
     /** Whether communal features are enabled. */
     val isCommunalEnabled: Boolean
         get() = communalRepository.isCommunalEnabled
@@ -80,21 +89,17 @@
     val isCommunalAvailable: StateFlow<Boolean> =
         flowOf(isCommunalEnabled)
             .flatMapLatest { enabled ->
-                if (enabled)
-                    combine(
-                        keyguardInteractor.isEncryptedOrLockdown,
-                        userRepository.selectedUserInfo,
-                        keyguardInteractor.isKeyguardVisible,
-                        keyguardInteractor.isDreaming,
-                    ) { isEncryptedOrLockdown, selectedUserInfo, isKeyguardVisible, isDreaming ->
-                        !isEncryptedOrLockdown &&
-                            selectedUserInfo.isMain &&
-                            (isKeyguardVisible || isDreaming)
-                    }
-                else flowOf(false)
+                if (enabled) {
+                    val isMainUser = userRepository.selectedUserInfo.map { it.isMain }
+                    and(
+                        isMainUser,
+                        not(keyguardInteractor.isEncryptedOrLockdown),
+                        or(keyguardInteractor.isKeyguardVisible, keyguardInteractor.isDreaming),
+                    )
+                } else {
+                    flowOf(false)
+                }
             }
-            .distinctUntilChanged()
-            .onEach { available -> widgetRepository.updateAppWidgetHostActive(available) }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
@@ -166,6 +171,10 @@
         communalRepository.setDesiredScene(newScene)
     }
 
+    fun setEditModeOpen(isOpen: Boolean) {
+        _editModeOpen.value = isOpen
+    }
+
     /** Show the widget editor Activity. */
     fun showWidgetEditor() {
         editWidgetsActivityStarter.startActivity()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 237a0c0..4b98f1a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -75,4 +75,7 @@
         _reorderingWidgets.value = false
         uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_CANCEL)
     }
+
+    /** Sets whether edit mode is currently open */
+    fun setEditModeOpen(isOpen: Boolean) = communalInteractor.setEditModeOpen(isOpen)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
new file mode 100644
index 0000000..586df32
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.communal.widgets
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.util.kotlin.BooleanFlowOperators.or
+import com.android.systemui.util.kotlin.pairwise
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.withContext
+
+@SysUISingleton
+class CommunalAppWidgetHostStartable
+@Inject
+constructor(
+    private val appWidgetHost: CommunalAppWidgetHost,
+    private val communalInteractor: CommunalInteractor,
+    @Background private val bgScope: CoroutineScope,
+    @Main private val uiDispatcher: CoroutineDispatcher
+) : CoreStartable {
+    override fun start() {
+        or(communalInteractor.isCommunalAvailable, communalInteractor.editModeOpen)
+            // Only trigger updates on state changes, ignoring the initial false value.
+            .pairwise(false)
+            .filter { (previous, new) -> previous != new }
+            .onEach { (_, shouldListen) -> updateAppWidgetHostActive(shouldListen) }
+            .launchIn(bgScope)
+    }
+
+    private suspend fun updateAppWidgetHostActive(active: Boolean) =
+        // Always ensure this is called on the main/ui thread.
+        withContext(uiDispatcher) {
+            if (active) {
+                appWidgetHost.startListening()
+            } else {
+                appWidgetHost.stopListening()
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index c7a14f9..a257543 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -86,6 +86,8 @@
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
+        communalViewModel.setEditModeOpen(true)
+
         val windowInsetsController = window.decorView.windowInsetsController
         windowInsetsController?.hide(WindowInsets.Type.systemBars())
         window.setDecorFitsSystemWindows(false)
@@ -138,13 +140,16 @@
 
     override fun onStart() {
         super.onStart()
-
         uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_EDIT_MODE_SHOWN)
     }
 
     override fun onStop() {
         super.onStop()
-
         uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_EDIT_MODE_GONE)
     }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        communalViewModel.setEditModeOpen(false)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 8d82b55..95233f7 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.biometrics.BiometricNotificationService
 import com.android.systemui.clipboardoverlay.ClipboardListener
 import com.android.systemui.communal.log.CommunalLoggerStartable
+import com.android.systemui.communal.widgets.CommunalAppWidgetHostStartable
 import com.android.systemui.controls.dagger.StartControlsStartableModule
 import com.android.systemui.dagger.qualifiers.PerUser
 import com.android.systemui.dreams.AssistantAttentionMonitor
@@ -324,4 +325,11 @@
     @IntoMap
     @ClassKey(CommunalLoggerStartable::class)
     abstract fun bindCommunalLoggerStartable(impl: CommunalLoggerStartable): CoreStartable
+
+    @Binds
+    @IntoMap
+    @ClassKey(CommunalAppWidgetHostStartable::class)
+    abstract fun bindCommunalAppWidgetHostStartable(
+        impl: CommunalAppWidgetHostStartable
+    ): CoreStartable
 }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
index 7a70c4a..cf7d601 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -40,8 +40,7 @@
 import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
 import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.BiometricType
 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
@@ -63,10 +62,6 @@
 import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.repository.UserRepository
 import com.google.errorprone.annotations.CompileTimeConstant
-import java.io.PrintWriter
-import java.util.Arrays
-import java.util.stream.Collectors
-import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
@@ -88,6 +83,10 @@
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
+import java.io.PrintWriter
+import java.util.Arrays
+import java.util.stream.Collectors
+import javax.inject.Inject
 
 /**
  * API to run face authentication and detection for device entry / on keyguard (as opposed to the
@@ -165,7 +164,6 @@
     @FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val displayStateInteractor: DisplayStateInteractor,
-    private val featureFlags: FeatureFlags,
     dumpManager: DumpManager,
 ) : DeviceEntryFaceAuthRepository, Dumpable {
     private var authCancellationSignal: CancellationSignal? = null
@@ -315,7 +313,7 @@
         // or device starts going to sleep.
         merge(
                 powerInteractor.isAsleep,
-                if (featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                if (KeyguardWmStateRefactor.isEnabled) {
                     keyguardTransitionInteractor.isInTransitionToState(KeyguardState.GONE)
                 } else {
                     keyguardRepository.keyguardDoneAnimationsFinished.map { true }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
index 08e8c2d..8283438 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
@@ -8,35 +8,26 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
-import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.user.data.repository.UserRepository
-import com.android.systemui.util.kotlin.sample
 import dagger.Binds
 import dagger.Module
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
-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.distinctUntilChanged
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.withContext
 
 /** Interface for classes that can access device-entry-related application state. */
 interface DeviceEntryRepository {
-    /** Whether the device is immediately entering the device after a biometric unlock. */
-    val enteringDeviceFromBiometricUnlock: Flow<BiometricUnlockSource>
-
     /**
      * Whether the device is unlocked.
      *
@@ -85,12 +76,6 @@
     keyguardStateController: KeyguardStateController,
     keyguardRepository: KeyguardRepository,
 ) : DeviceEntryRepository {
-    override val enteringDeviceFromBiometricUnlock =
-        keyguardRepository.biometricUnlockState
-            .filter { BiometricUnlockModel.dismissesKeyguard(it) }
-            .sample(
-                keyguardRepository.biometricUnlockSource.filterNotNull(),
-            )
 
     private val _isUnlocked = MutableStateFlow(false)
 
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractor.kt
new file mode 100644
index 0000000..337fe1e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractor.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.deviceentry.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+
+/** Business logic for device entry auth ripple interactions. */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class AuthRippleInteractor
+@Inject
+constructor(
+    deviceEntrySourceInteractor: DeviceEntrySourceInteractor,
+    deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
+) {
+    private val showUnlockRippleFromDeviceEntryIcon: Flow<BiometricUnlockSource> =
+        deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { isUdfpsSupported ->
+            if (isUdfpsSupported) {
+                deviceEntrySourceInteractor.deviceEntryFromDeviceEntryIcon.map {
+                    BiometricUnlockSource.FINGERPRINT_SENSOR
+                }
+            } else {
+                emptyFlow()
+            }
+        }
+
+    private val showUnlockRippleFromBiometricUnlock: Flow<BiometricUnlockSource> =
+        deviceEntrySourceInteractor.deviceEntryFromBiometricSource
+    val showUnlockRipple: Flow<BiometricUnlockSource> =
+        merge(
+            showUnlockRippleFromDeviceEntryIcon,
+            showUnlockRippleFromBiometricUnlock,
+        )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
index 649a971..782bce4 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
@@ -46,7 +46,7 @@
 class DeviceEntryHapticsInteractor
 @Inject
 constructor(
-    deviceEntryInteractor: DeviceEntryInteractor,
+    deviceEntrySourceInteractor: DeviceEntrySourceInteractor,
     deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
     deviceEntryBiometricAuthInteractor: DeviceEntryBiometricAuthInteractor,
     fingerprintPropertyRepository: FingerprintPropertyRepository,
@@ -80,7 +80,7 @@
             }
 
     val playSuccessHaptic: Flow<Unit> =
-        deviceEntryInteractor.enteringDeviceFromBiometricUnlock
+        deviceEntrySourceInteractor.deviceEntryFromBiometricSource
             .sample(
                 combine(
                     powerButtonSideFpsEnrolled,
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 0985357..73389cb 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -23,7 +23,6 @@
 import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
 import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
 import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.scene.shared.model.SceneKey
@@ -31,7 +30,6 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.collectLatest
@@ -55,7 +53,7 @@
 @Inject
 constructor(
     @Application private val applicationScope: CoroutineScope,
-    repository: DeviceEntryRepository,
+    private val repository: DeviceEntryRepository,
     private val authenticationInteractor: AuthenticationInteractor,
     private val sceneInteractor: SceneInteractor,
     deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository,
@@ -63,9 +61,6 @@
     flags: SceneContainerFlags,
     deviceUnlockedInteractor: DeviceUnlockedInteractor,
 ) {
-    val enteringDeviceFromBiometricUnlock: Flow<BiometricUnlockSource> =
-        repository.enteringDeviceFromBiometricUnlock
-
     /**
      * Whether the device is unlocked.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt
new file mode 100644
index 0000000..d4f76a8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.deviceentry.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+
+/**
+ * Hosts application business logic related to the source of the user entering the device. Note: The
+ * source of the user entering the device isn't equivalent to the reason the device is unlocked.
+ *
+ * For example, the user successfully enters the device when they dismiss the lockscreen via a
+ * bypass biometric or, if the device is already unlocked, by triggering an affordance that
+ * dismisses the lockscreen.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class DeviceEntrySourceInteractor
+@Inject
+constructor(
+    keyguardInteractor: KeyguardInteractor,
+) {
+    val deviceEntryFromBiometricSource: Flow<BiometricUnlockSource> =
+        keyguardInteractor.biometricUnlockState
+            .filter { BiometricUnlockModel.dismissesKeyguard(it) }
+            .sample(
+                keyguardInteractor.biometricUnlockSource.filterNotNull(),
+            )
+
+    private val attemptEnterDeviceFromDeviceEntryIcon: MutableSharedFlow<Unit> = MutableSharedFlow()
+    val deviceEntryFromDeviceEntryIcon: Flow<Unit> =
+        attemptEnterDeviceFromDeviceEntryIcon
+            .sample(keyguardInteractor.isKeyguardDismissible)
+            .filter { it } // only send events if the keyguard is dismissible
+            .map {} // map to Unit
+
+    suspend fun attemptEnterDeviceFromDeviceEntryIcon() {
+        attemptEnterDeviceFromDeviceEntryIcon.emit(Unit)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index d2883cc..c69c9ef 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -220,19 +220,6 @@
     @JvmField
     val WALLPAPER_PICKER_PREVIEW_ANIMATION = releasedFlag("wallpaper_picker_preview_animation")
 
-    /**
-     * TODO(b/278086361): Tracking bug
-     * Complete rewrite of the interactions between System UI and Window Manager involving keyguard
-     * state. When enabled, calls to ActivityTaskManagerService from System UI will exclusively
-     * occur from [WmLockscreenVisibilityManager] rather than the legacy KeyguardViewMediator.
-     *
-     * This flag is under development; some types of unlock may not animate properly if you enable
-     * it.
-     */
-    @JvmField
-    val KEYGUARD_WM_STATE_REFACTOR: UnreleasedFlag =
-            unreleasedFlag("keyguard_wm_state_refactor")
-
     // 300 - power menu
     // TODO(b/254512600): Tracking Bug
     @JvmField val POWER_MENU_LITE = releasedFlag("power_menu_lite")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index e2ab20e..f10b87e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -77,7 +77,6 @@
 import com.android.systemui.SystemUIApplication;
 import com.android.systemui.dagger.qualifiers.Application;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier;
 import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindViewBinder;
 import com.android.systemui.keyguard.ui.binder.WindowManagerLockscreenVisibilityViewBinder;
@@ -329,7 +328,7 @@
         mFlags = featureFlags;
         mPowerInteractor = powerInteractor;
 
-        if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+        if (KeyguardWmStateRefactor.isEnabled()) {
             WindowManagerLockscreenVisibilityViewBinder.bind(
                     wmLockscreenVisibilityViewModel,
                     wmLockscreenVisibilityManager,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 01ba0d2..53c81e5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -419,7 +419,7 @@
      */
     fun canPerformInWindowLauncherAnimations(): Boolean {
         // TODO(b/278086361): Refactor in-window animations.
-        return !featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR) &&
+        return !KeyguardWmStateRefactor.isEnabled &&
                 isSupportedLauncherUnderneath() &&
                 // If the launcher is underneath, but we're about to launch an activity, don't do
                 // the animations since they won't be visible.
@@ -866,7 +866,7 @@
         }
 
         surfaceBehindRemoteAnimationTargets?.forEach { surfaceBehindRemoteAnimationTarget ->
-            if (!featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            if (!KeyguardWmStateRefactor.isEnabled) {
                 val surfaceHeight: Int =
                         surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
 
@@ -1005,7 +1005,7 @@
         if (keyguardStateController.isShowing) {
             // Hide the keyguard, with no fade out since we animated it away during the unlock.
 
-            if (!featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            if (!KeyguardWmStateRefactor.isEnabled) {
                 keyguardViewController.hide(
                         surfaceBehindRemoteAnimationStartTime,
                         0 /* fadeOutDuration */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 50caf17..8e3b196 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -139,7 +139,6 @@
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.flags.SystemPropertiesHelper;
 import com.android.systemui.keyguard.dagger.KeyguardModule;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
@@ -175,8 +174,6 @@
 import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
 import com.android.wm.shell.keyguard.KeyguardTransitions;
 
-import dagger.Lazy;
-
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -186,6 +183,7 @@
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
+import dagger.Lazy;
 import kotlinx.coroutines.CoroutineDispatcher;
 
 /**
@@ -1051,7 +1049,7 @@
                 IRemoteAnimationFinishedCallback finishedCallback) {
             Trace.beginSection("mExitAnimationRunner.onAnimationStart#startKeyguardExitAnimation");
             startKeyguardExitAnimation(transit, apps, wallpapers, nonApps, finishedCallback);
-            if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            if (KeyguardWmStateRefactor.isEnabled()) {
                 mWmLockscreenVisibilityManager.get().onKeyguardGoingAwayRemoteAnimationStart(
                         transit, apps, wallpapers, nonApps, finishedCallback);
             }
@@ -1061,7 +1059,7 @@
         @Override // Binder interface
         public void onAnimationCancelled() {
             cancelKeyguardExitAnimation();
-            if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            if (KeyguardWmStateRefactor.isEnabled()) {
                 mWmLockscreenVisibilityManager.get().onKeyguardGoingAwayRemoteAnimationCancelled();
             }
         }
@@ -2757,7 +2755,7 @@
         mUiBgExecutor.execute(() -> {
             Log.d(TAG, "updateActivityLockScreenState(" + showing + ", " + aodShowing + ")");
 
-            if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            if (KeyguardWmStateRefactor.isEnabled()) {
                 // Handled in WmLockscreenVisibilityManager if flag is enabled.
                 return;
             }
@@ -2811,7 +2809,7 @@
             setShowingLocked(true, hidingOrGoingAway /* force */);
             mHiding = false;
 
-            if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            if (!KeyguardWmStateRefactor.isEnabled()) {
                 // Handled directly in StatusBarKeyguardViewManager if enabled.
                 mKeyguardViewControllerLazy.get().show(options);
             }
@@ -2888,7 +2886,7 @@
             mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(true);
 
             // Handled in WmLockscreenVisibilityManager if flag is enabled.
-            if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            if (!KeyguardWmStateRefactor.isEnabled()) {
                     // Don't actually hide the Keyguard at the moment, wait for window manager 
                     // until it tells us it's safe to do so with startKeyguardExitAnimation.
 		    // Posting to mUiOffloadThread to ensure that calls to ActivityTaskManager 
@@ -2994,7 +2992,7 @@
             } else {
                 Log.d(TAG, "Hiding keyguard while occluded. Just hide the keyguard view and exit.");
 
-                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                if (!KeyguardWmStateRefactor.isEnabled()) {
                     mKeyguardViewControllerLazy.get().hide(
                             mSystemClock.uptimeMillis() + mHideAnimation.getStartOffset(),
                             mHideAnimation.getDuration());
@@ -3030,7 +3028,7 @@
                 // If the flag is enabled, remote animation state is handled in
                 // WmLockscreenVisibilityManager.
                 if (finishedCallback != null
-                        && !mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                        && !KeyguardWmStateRefactor.isEnabled()) {
                     // There will not execute animation, send a finish callback to ensure the remote
                     // animation won't hang there.
                     try {
@@ -3056,7 +3054,7 @@
                         new IRemoteAnimationFinishedCallback() {
                             @Override
                             public void onAnimationFinished() throws RemoteException {
-                                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                                if (!KeyguardWmStateRefactor.isEnabled()) {
                                     try {
                                         finishedCallback.onAnimationFinished();
                                     } catch (RemoteException e) {
@@ -3088,7 +3086,7 @@
             // it will dismiss the panel in that case.
             } else if (!mStatusBarStateController.leaveOpenOnKeyguardHide()
                     && apps != null && apps.length > 0) {
-                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                if (!KeyguardWmStateRefactor.isEnabled()) {
                     // Handled in WmLockscreenVisibilityManager. Other logic in this class will
                     // short circuit when this is null.
                     mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback;
@@ -3112,7 +3110,7 @@
                         createInteractionJankMonitorConf(
                                 CUJ_LOCKSCREEN_UNLOCK_ANIMATION, "RemoteAnimationDisabled"));
 
-                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                if (!KeyguardWmStateRefactor.isEnabled()) {
                     // Handled directly in StatusBarKeyguardViewManager if enabled.
                     mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration);
                 }
@@ -3131,7 +3129,7 @@
                         Slog.e(TAG, "Keyguard exit without a corresponding app to show.");
 
                         try {
-                            if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                            if (!KeyguardWmStateRefactor.isEnabled()) {
                                 finishedCallback.onAnimationFinished();
                             }
                         } catch (RemoteException e) {
@@ -3163,7 +3161,7 @@
                         @Override
                         public void onAnimationEnd(Animator animation) {
                             try {
-                                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                                if (!KeyguardWmStateRefactor.isEnabled()) {
                                     finishedCallback.onAnimationFinished();
                                 }
                             } catch (RemoteException e) {
@@ -3176,7 +3174,7 @@
                         @Override
                         public void onAnimationCancel(Animator animation) {
                             try {
-                                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                                if (!KeyguardWmStateRefactor.isEnabled()) {
                                     finishedCallback.onAnimationFinished();
                                 }
                             } catch (RemoteException e) {
@@ -3341,7 +3339,7 @@
                 flags |= KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
             }
 
-            if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            if (!KeyguardWmStateRefactor.isEnabled()) {
                 // Handled in WmLockscreenVisibilityManager.
                 mActivityTaskManagerService.keyguardGoingAway(flags);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmStateRefactor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmStateRefactor.kt
new file mode 100644
index 0000000..ddccc5d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmStateRefactor.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the keyguard wm state refactor flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object KeyguardWmStateRefactor {
+    /** The aconfig flag name */
+    const val FLAG_NAME = Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
+
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
+    /** Is the refactor enabled */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.keyguardWmStateRefactor()
+
+    /**
+     * Called to ensure code is only run when the flag is enabled. This protects users from the
+     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+     * build to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun isUnexpectedlyInLegacyMode() =
+        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is enabled to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 1437194..1c6056c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -101,7 +101,7 @@
      * Whether the device is locked or unlocked right now. This is true when keyguard has been
      * dismissed or can be dismissed by a swipe
      */
-    val isKeyguardUnlocked: StateFlow<Boolean>
+    val isKeyguardDismissible: StateFlow<Boolean>
 
     /**
      * Observable for the signal that keyguard is about to go away.
@@ -388,7 +388,7 @@
             }
             .distinctUntilChanged()
 
-    override val isKeyguardUnlocked: StateFlow<Boolean> =
+    override val isKeyguardDismissible: StateFlow<Boolean> =
         conflatedCallbackFlow {
                 val callback =
                     object : KeyguardStateController.Callback {
@@ -396,7 +396,7 @@
                             trySendWithFailureLogging(
                                 keyguardStateController.isUnlocked,
                                 TAG,
-                                "updated isKeyguardUnlocked due to onUnlockedChanged"
+                                "updated isKeyguardDismissible due to onUnlockedChanged"
                             )
                         }
 
@@ -404,7 +404,7 @@
                             trySendWithFailureLogging(
                                 keyguardStateController.isUnlocked,
                                 TAG,
-                                "updated isKeyguardUnlocked due to onKeyguardShowingChanged"
+                                "updated isKeyguardDismissible due to onKeyguardShowingChanged"
                             )
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 8b2b45f..3965648 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -23,7 +23,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
+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
@@ -230,7 +230,7 @@
                     combine(
                         startedKeyguardTransitionStep,
                         keyguardInteractor.statusBarState,
-                        keyguardInteractor.isKeyguardUnlocked,
+                        keyguardInteractor.isKeyguardDismissible,
                         ::Triple
                     ),
                     ::toQuad
@@ -307,7 +307,7 @@
     }
 
     private fun listenForLockscreenToGone() {
-        if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+        if (KeyguardWmStateRefactor.isEnabled) {
             return
         }
 
@@ -324,7 +324,7 @@
     }
 
     private fun listenForLockscreenToGoneDragging() {
-        if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+        if (KeyguardWmStateRefactor.isEnabled) {
             return
         }
 
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 33b6373..acbd9fb 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
@@ -23,7 +23,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
+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
@@ -217,7 +217,7 @@
     }
 
     private fun listenForPrimaryBouncerToGone() {
-        if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+        if (KeyguardWmStateRefactor.isEnabled) {
             // This is handled in KeyguardSecurityContainerController and
             // StatusBarKeyguardViewManager, which calls the transition interactor to kick off a
             // transition vs. listening to legacy state flags.
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 36bd905..22d11d0 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
@@ -32,6 +32,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
@@ -162,8 +163,8 @@
     /** Whether the keyguard is showing or not. */
     val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
 
-    /** Whether the keyguard is unlocked or not. */
-    val isKeyguardUnlocked: Flow<Boolean> = repository.isKeyguardUnlocked
+    /** Whether the keyguard is dismissible or not. */
+    val isKeyguardDismissible: Flow<Boolean> = repository.isKeyguardDismissible
 
     /** Whether the keyguard is occluded (covered by an activity). */
     val isKeyguardOccluded: Flow<Boolean> = repository.isKeyguardOccluded
@@ -194,6 +195,9 @@
     /** Observable for the [StatusBarState] */
     val statusBarState: Flow<StatusBarState> = repository.statusBarState
 
+    /** Source of the most recent biometric unlock, such as fingerprint or face. */
+    val biometricUnlockSource: Flow<BiometricUnlockSource?> = repository.biometricUnlockSource
+
     /**
      * Observable for [BiometricUnlockModel] when biometrics like face or any fingerprint (rear,
      * side, under display) is used to unlock the device.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
index a02e8ac..703bb87 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.VibratorHelper
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.launch
 
@@ -48,6 +49,7 @@
     @SuppressLint("ClickableViewAccessibility")
     @JvmStatic
     fun bind(
+        applicationScope: CoroutineScope,
         view: DeviceEntryIconView,
         viewModel: DeviceEntryIconViewModel,
         fgViewModel: DeviceEntryForegroundViewModel,
@@ -69,7 +71,7 @@
                         view,
                         HapticFeedbackConstants.CONFIRM,
                     )
-                    viewModel.onLongPress()
+                    applicationScope.launch { viewModel.onLongPress() }
                 }
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
index bc6c7cb..ad589df 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
@@ -120,8 +120,20 @@
             } else {
                 connect(nicId, TOP, R.id.keyguard_status_view, topAlignment, bottomMargin)
             }
-            connect(nicId, START, PARENT_ID, START)
-            connect(nicId, END, PARENT_ID, END)
+            connect(
+                nicId,
+                START,
+                PARENT_ID,
+                START,
+                context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
+            )
+            connect(
+                nicId,
+                END,
+                PARENT_ID,
+                END,
+                context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
+            )
             constrainHeight(
                 nicId,
                 context.resources.getDimensionPixelSize(R.dimen.notification_shelf_height)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index a1b3f27..fe4f07d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -31,6 +31,7 @@
 import androidx.constraintlayout.widget.ConstraintSet.VISIBLE
 import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
 import com.android.systemui.Flags
+import com.android.systemui.customization.R as customizationR
 import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardSection
@@ -160,16 +161,14 @@
             var largeClockTopMargin =
                 context.resources.getDimensionPixelSize(R.dimen.status_bar_height) +
                     context.resources.getDimensionPixelSize(
-                        com.android.systemui.customization.R.dimen.small_clock_padding_top
+                        customizationR.dimen.small_clock_padding_top
                     ) +
                     context.resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset)
             largeClockTopMargin += getDimen(DATE_WEATHER_VIEW_HEIGHT)
             largeClockTopMargin += getDimen(ENHANCED_SMARTSPACE_HEIGHT)
             if (!keyguardClockViewModel.useLargeClock) {
                 largeClockTopMargin -=
-                    context.resources.getDimensionPixelSize(
-                        com.android.systemui.customization.R.dimen.small_clock_height
-                    )
+                    context.resources.getDimensionPixelSize(customizationR.dimen.small_clock_height)
             }
             connect(R.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin)
             constrainHeight(R.id.lockscreen_clock_view_large, WRAP_CONTENT)
@@ -177,18 +176,15 @@
             constrainWidth(R.id.lockscreen_clock_view, WRAP_CONTENT)
             constrainHeight(
                 R.id.lockscreen_clock_view,
-                context.resources.getDimensionPixelSize(
-                    com.android.systemui.customization.R.dimen.small_clock_height
-                )
+                context.resources.getDimensionPixelSize(customizationR.dimen.small_clock_height)
             )
             connect(
                 R.id.lockscreen_clock_view,
                 START,
                 PARENT_ID,
                 START,
-                context.resources.getDimensionPixelSize(
-                    com.android.systemui.customization.R.dimen.clock_padding_start
-                )
+                context.resources.getDimensionPixelSize(customizationR.dimen.clock_padding_start) +
+                    context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
             )
             var smallClockTopMargin =
                 if (splitShadeStateController.shouldUseSplitNotificationShade(context.resources)) {
@@ -199,9 +195,7 @@
                 }
             if (keyguardClockViewModel.useLargeClock) {
                 smallClockTopMargin -=
-                    context.resources.getDimensionPixelSize(
-                        com.android.systemui.customization.R.dimen.small_clock_height
-                    )
+                    context.resources.getDimensionPixelSize(customizationR.dimen.small_clock_height)
             }
             connect(R.id.lockscreen_clock_view, TOP, PARENT_ID, TOP, smallClockTopMargin)
         }
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 0bf9ad0..3fc9b42 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
@@ -31,6 +31,7 @@
 import com.android.keyguard.LockIconViewController
 import com.android.systemui.Flags.keyguardBottomAreaRefactor
 import com.android.systemui.biometrics.AuthController
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
@@ -46,6 +47,7 @@
 import com.android.systemui.statusbar.VibratorHelper
 import dagger.Lazy
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 /** Includes the device entry icon. */
@@ -53,6 +55,7 @@
 class DefaultDeviceEntrySection
 @Inject
 constructor(
+    @Application private val applicationScope: CoroutineScope,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val authController: AuthController,
     private val windowManager: WindowManager,
@@ -91,6 +94,7 @@
         if (DeviceEntryUdfpsRefactor.isEnabled) {
             constraintLayout.findViewById<DeviceEntryIconView?>(deviceEntryIconViewId)?.let {
                 DeviceEntryIconViewBinder.bind(
+                    applicationScope,
                     it,
                     deviceEntryIconViewModel.get(),
                     deviceEntryForegroundViewModel.get(),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
index 8c5e9b4..d75a72f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
@@ -18,7 +18,6 @@
 package com.android.systemui.keyguard.ui.view.layout.sections
 
 import android.content.Context
-import android.view.View
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
 import androidx.constraintlayout.widget.ConstraintSet.END
@@ -67,7 +66,6 @@
         notificationStackSizeCalculator,
         mainDispatcher,
     ) {
-    private val smartSpaceBarrier = View.generateViewId()
     override fun applyConstraints(constraintSet: ConstraintSet) {
         if (!KeyguardShadeMigrationNssl.isEnabled) {
             return
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
index 37842a8..2f99719 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
@@ -31,7 +31,8 @@
 import com.android.systemui.keyguard.ui.binder.KeyguardSmartspaceViewBinder
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
-import com.android.systemui.shared.R
+import com.android.systemui.res.R as R
+import com.android.systemui.shared.R as sharedR
 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
 import dagger.Lazy
 import javax.inject.Inject
@@ -100,94 +101,94 @@
         if (!migrateClocksToBlueprint()) {
             return
         }
+        val horizontalPaddingStart =
+            context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_start) +
+                context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
+        val horizontalPaddingEnd =
+            context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_end) +
+                context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
         constraintSet.apply {
             // migrate addDateWeatherView, addWeatherView from KeyguardClockSwitchController
-            constrainHeight(R.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
-            constrainWidth(R.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
+            constrainHeight(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
+            constrainWidth(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
             connect(
-                R.id.date_smartspace_view,
+                sharedR.id.date_smartspace_view,
                 ConstraintSet.START,
                 ConstraintSet.PARENT_ID,
                 ConstraintSet.START,
-                context.resources.getDimensionPixelSize(
-                    com.android.systemui.res.R.dimen.below_clock_padding_start
-                )
+                horizontalPaddingStart
             )
-            constrainWidth(R.id.weather_smartspace_view, ConstraintSet.WRAP_CONTENT)
+            constrainWidth(sharedR.id.weather_smartspace_view, ConstraintSet.WRAP_CONTENT)
             connect(
-                R.id.weather_smartspace_view,
+                sharedR.id.weather_smartspace_view,
                 ConstraintSet.TOP,
-                R.id.date_smartspace_view,
+                sharedR.id.date_smartspace_view,
                 ConstraintSet.TOP
             )
             connect(
-                R.id.weather_smartspace_view,
+                sharedR.id.weather_smartspace_view,
                 ConstraintSet.BOTTOM,
-                R.id.date_smartspace_view,
+                sharedR.id.date_smartspace_view,
                 ConstraintSet.BOTTOM
             )
             connect(
-                R.id.weather_smartspace_view,
+                sharedR.id.weather_smartspace_view,
                 ConstraintSet.START,
-                R.id.date_smartspace_view,
+                sharedR.id.date_smartspace_view,
                 ConstraintSet.END,
                 4
             )
 
             // migrate addSmartspaceView from KeyguardClockSwitchController
-            constrainHeight(R.id.bc_smartspace_view, ConstraintSet.WRAP_CONTENT)
+            constrainHeight(sharedR.id.bc_smartspace_view, ConstraintSet.WRAP_CONTENT)
             connect(
-                R.id.bc_smartspace_view,
+                sharedR.id.bc_smartspace_view,
                 ConstraintSet.START,
                 ConstraintSet.PARENT_ID,
                 ConstraintSet.START,
-                context.resources.getDimensionPixelSize(
-                    com.android.systemui.res.R.dimen.below_clock_padding_start
-                )
+                horizontalPaddingStart
             )
             connect(
-                R.id.bc_smartspace_view,
+                sharedR.id.bc_smartspace_view,
                 ConstraintSet.END,
                 if (keyguardClockViewModel.clockShouldBeCentered.value) ConstraintSet.PARENT_ID
-                else com.android.systemui.res.R.id.split_shade_guideline,
+                else R.id.split_shade_guideline,
                 ConstraintSet.END,
-                context.resources.getDimensionPixelSize(
-                    com.android.systemui.res.R.dimen.below_clock_padding_end
-                )
+                horizontalPaddingEnd
             )
 
             if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) {
-                clear(R.id.date_smartspace_view, ConstraintSet.TOP)
+                clear(sharedR.id.date_smartspace_view, ConstraintSet.TOP)
                 connect(
-                    R.id.date_smartspace_view,
+                    sharedR.id.date_smartspace_view,
                     ConstraintSet.BOTTOM,
-                    R.id.bc_smartspace_view,
+                    sharedR.id.bc_smartspace_view,
                     ConstraintSet.TOP
                 )
             } else {
-                clear(R.id.date_smartspace_view, ConstraintSet.BOTTOM)
+                clear(sharedR.id.date_smartspace_view, ConstraintSet.BOTTOM)
                 connect(
-                    R.id.date_smartspace_view,
+                    sharedR.id.date_smartspace_view,
                     ConstraintSet.TOP,
-                    com.android.systemui.res.R.id.lockscreen_clock_view,
+                    R.id.lockscreen_clock_view,
                     ConstraintSet.BOTTOM
                 )
                 connect(
-                    R.id.bc_smartspace_view,
+                    sharedR.id.bc_smartspace_view,
                     ConstraintSet.TOP,
-                    R.id.date_smartspace_view,
+                    sharedR.id.date_smartspace_view,
                     ConstraintSet.BOTTOM
                 )
             }
 
             createBarrier(
-                com.android.systemui.res.R.id.smart_space_barrier_bottom,
+                R.id.smart_space_barrier_bottom,
                 Barrier.BOTTOM,
                 0,
                 *intArrayOf(
-                    R.id.bc_smartspace_view,
-                    R.id.date_smartspace_view,
-                    R.id.weather_smartspace_view,
+                    sharedR.id.bc_smartspace_view,
+                    sharedR.id.date_smartspace_view,
+                    sharedR.id.weather_smartspace_view,
                 )
             )
         }
@@ -212,7 +213,7 @@
     private fun updateVisibility(constraintSet: ConstraintSet) {
         constraintSet.apply {
             setVisibility(
-                R.id.weather_smartspace_view,
+                sharedR.id.weather_smartspace_view,
                 when (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) {
                     true -> ConstraintSet.GONE
                     false ->
@@ -223,7 +224,7 @@
                 }
             )
             setVisibility(
-                R.id.date_smartspace_view,
+                sharedR.id.date_smartspace_view,
                 if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) ConstraintSet.GONE
                 else ConstraintSet.VISIBLE
             )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index eacaa40..a3d5453 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -20,6 +20,7 @@
 import android.animation.IntEvaluator
 import com.android.keyguard.KeyguardViewController
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntrySourceInteractor
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -56,6 +57,7 @@
     private val sceneContainerFlags: SceneContainerFlags,
     private val keyguardViewController: Lazy<KeyguardViewController>,
     private val deviceEntryInteractor: DeviceEntryInteractor,
+    private val deviceEntrySourceInteractor: DeviceEntrySourceInteractor,
 ) {
     private val intEvaluator = IntEvaluator()
     private val floatEvaluator = FloatEvaluator()
@@ -208,14 +210,13 @@
             }
         }
 
-    fun onLongPress() {
-        // TODO (b/309804148): play auth ripple via an interactor
-
+    suspend fun onLongPress() {
         if (sceneContainerFlags.isEnabled()) {
             deviceEntryInteractor.attemptDeviceEntry()
         } else {
             keyguardViewController.get().showPrimaryBouncer(/* scrim */ true)
         }
+        deviceEntrySourceInteractor.attemptEnterDeviceFromDeviceEntryIcon()
     }
 
     private fun DeviceEntryIconView.IconType.toAccessibilityHintType():
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
index 23ee00d..a3029b2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
@@ -146,7 +146,7 @@
                 null,
                 UserHandle.ALL
             )
-            userTracker.addCallback(userTrackerCallback, mainExecutor)
+            userTracker.addCallback(userTrackerCallback, backgroundExecutor)
             loadSavedComponents()
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index fa03dc2..93a6eee 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -34,6 +34,8 @@
 import androidx.core.os.postDelayed
 import androidx.core.view.isVisible
 import androidx.dynamicanimation.animation.DynamicAnimation
+import com.android.internal.jank.Cuj.CUJ_BACK_PANEL_ARROW
+import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.util.LatencyTracker
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.NavigationEdgeBackPlugin
@@ -86,6 +88,7 @@
     private val vibratorHelper: VibratorHelper,
     private val configurationController: ConfigurationController,
     private val latencyTracker: LatencyTracker,
+    private val interactionJankMonitor: InteractionJankMonitor,
 ) : ViewController<BackPanel>(BackPanel(context, latencyTracker)), NavigationEdgeBackPlugin {
 
     /**
@@ -103,6 +106,7 @@
         private val vibratorHelper: VibratorHelper,
         private val configurationController: ConfigurationController,
         private val latencyTracker: LatencyTracker,
+        private val interactionJankMonitor: InteractionJankMonitor,
     ) {
         /** Construct a [BackPanelController]. */
         fun create(context: Context): BackPanelController {
@@ -115,6 +119,7 @@
                     vibratorHelper,
                     configurationController,
                     latencyTracker,
+                    interactionJankMonitor
                 )
             backPanelController.init()
             return backPanelController
@@ -183,7 +188,7 @@
         /* Arrow is animating in */
         ENTRY,
 
-        /* could be entry, neutral, or stretched, releasing will commit back */
+        /* releasing will commit back */
         ACTIVE,
 
         /* releasing will cancel back */
@@ -366,6 +371,7 @@
                 // Receiving a CANCEL implies that something else intercepted
                 // the gesture, i.e., the user did not cancel their gesture.
                 // Therefore, disappear immediately, with minimum fanfare.
+                interactionJankMonitor.cancel(CUJ_BACK_PANEL_ARROW)
                 updateArrowState(GestureState.GONE)
                 velocityTracker = null
             }
@@ -813,7 +819,7 @@
                     scale =
                         when (currentState) {
                             GestureState.ACTIVE,
-                            GestureState.FLUNG, -> params.activeIndicator.scale
+                            GestureState.FLUNG -> params.activeIndicator.scale
                             GestureState.COMMITTED -> params.committedIndicator.scale
                             else -> params.preThresholdIndicator.scale
                         },
@@ -877,6 +883,16 @@
         previousState = currentState
         currentState = newState
 
+        // First, update the jank tracker
+        when (currentState) {
+            GestureState.ENTRY -> {
+                interactionJankMonitor.cancel(CUJ_BACK_PANEL_ARROW)
+                interactionJankMonitor.begin(mView, CUJ_BACK_PANEL_ARROW)
+            }
+            GestureState.GONE -> interactionJankMonitor.end(CUJ_BACK_PANEL_ARROW)
+            else -> {}
+        }
+
         when (currentState) {
             GestureState.CANCELLED -> {
                 backCallback.cancelBack()
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 58e0428..91c86df 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -84,6 +84,7 @@
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputChannelCompat;
+import com.android.systemui.shared.system.InputMonitorCompat;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.shared.system.TaskStackChangeListener;
@@ -261,7 +262,7 @@
     private boolean mIsTrackpadThreeFingerSwipe;
     private boolean mIsButtonForcedVisible;
 
-    private InputMonitor mInputMonitor;
+    private InputMonitorCompat mInputMonitor;
     private InputChannelCompat.InputEventReceiver mInputEventReceiver;
 
     private NavigationEdgeBackPlugin mEdgeBackPlugin;
@@ -665,10 +666,8 @@
                 }
 
                 // Register input event receiver
-                mInputMonitor = mContext.getSystemService(InputManager.class).monitorGestureInput(
-                        "edge-swipe", mDisplayId);
-                mInputEventReceiver = new InputChannelCompat.InputEventReceiver(
-                        mInputMonitor.getInputChannel(), Looper.getMainLooper(),
+                mInputMonitor = new InputMonitorCompat("edge-swipe", mDisplayId);
+                mInputEventReceiver = mInputMonitor.getInputReceiver(Looper.getMainLooper(),
                         Choreographer.getInstance(), this::onInputEvent);
 
                 // Add a nav bar panel window
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 958ace35..21de185 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -26,6 +26,8 @@
 import android.database.ContentObserver;
 import android.os.BatteryManager;
 import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
 import android.os.IThermalEventListener;
 import android.os.IThermalService;
 import android.os.PowerManager;
@@ -95,6 +97,7 @@
     private Future mLastShowWarningTask;
     private boolean mEnableSkinTemperatureWarning;
     private boolean mEnableUsbTemperatureAlarm;
+    private final HandlerThread mHandlerThread;
 
     private int mLowBatteryAlertCloseLevel;
     private final int[] mLowBatteryReminderLevels = new int[2];
@@ -167,6 +170,8 @@
         mPowerManager = powerManager;
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mUserTracker = userTracker;
+        mHandlerThread = new HandlerThread("PowerUI");
+        mHandlerThread.start();
     }
 
     public void start() {
@@ -185,7 +190,8 @@
                 false, obs, UserHandle.USER_ALL);
         updateBatteryWarningLevels();
         mReceiver.init();
-        mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
+        mUserTracker.addCallback(mUserChangedCallback,
+                    new HandlerExecutor(mHandlerThread.getThreadHandler()));
         mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
 
         // Check to see if we need to let the user know that the phone previously shut down due
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt
index adea26e..e1ec338 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt
@@ -18,6 +18,8 @@
 
 import android.content.res.Resources
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.pipeline.domain.autoaddable.A11yShortcutAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.A11yShortcutAutoAddableList
 import com.android.systemui.qs.pipeline.domain.autoaddable.AutoAddableSetting
 import com.android.systemui.qs.pipeline.domain.autoaddable.AutoAddableSettingList
 import com.android.systemui.qs.pipeline.domain.autoaddable.CastAutoAddable
@@ -51,6 +53,16 @@
                 )
                 .toSet()
         }
+
+        @Provides
+        @ElementsIntoSet
+        fun providesA11yShortcutAutoAddable(
+            a11yShortcutAutoAddableFactory: A11yShortcutAutoAddable.Factory
+        ): Set<AutoAddable> {
+            return A11yShortcutAutoAddableList.getA11yShortcutAutoAddables(
+                a11yShortcutAutoAddableFactory
+            )
+        }
     }
 
     @Binds @IntoSet fun bindCastAutoAddable(impl: CastAutoAddable): AutoAddable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
index bcd09bd..dc39c97 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
@@ -25,6 +25,7 @@
 import android.content.pm.PackageManager.ResolveInfoFlags
 import android.os.UserHandle
 import android.service.quicksettings.TileService
+import androidx.annotation.GuardedBy
 import com.android.systemui.common.data.repository.PackageChangeRepository
 import com.android.systemui.common.data.shared.model.PackageChangeModel
 import com.android.systemui.dagger.SysUISingleton
@@ -32,12 +33,13 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.util.kotlin.isComponentActuallyEnabled
 import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
 
 interface InstalledTilesComponentRepository {
 
@@ -49,33 +51,39 @@
 @Inject
 constructor(
     @Application private val applicationContext: Context,
-    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    @Background private val backgroundScope: CoroutineScope,
     private val packageChangeRepository: PackageChangeRepository
 ) : InstalledTilesComponentRepository {
 
-    override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> {
-        /*
-         * In order to query [PackageManager] for different users, this implementation will call
-         * [Context.createContextAsUser] and retrieve the [PackageManager] from that context.
-         */
-        val packageManager =
-            if (applicationContext.userId == userId) {
-                applicationContext.packageManager
-            } else {
-                applicationContext
-                    .createContextAsUser(
-                        UserHandle.of(userId),
-                        /* flags */ 0,
-                    )
-                    .packageManager
+    @GuardedBy("userMap") private val userMap = mutableMapOf<Int, Flow<Set<ComponentName>>>()
+
+    override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> =
+        synchronized(userMap) {
+            userMap.getOrPut(userId) {
+                /*
+                 * In order to query [PackageManager] for different users, this implementation will
+                 * call [Context.createContextAsUser] and retrieve the [PackageManager] from that
+                 * context.
+                 */
+                val packageManager =
+                    if (applicationContext.userId == userId) {
+                        applicationContext.packageManager
+                    } else {
+                        applicationContext
+                            .createContextAsUser(
+                                UserHandle.of(userId),
+                                /* flags */ 0,
+                            )
+                            .packageManager
+                    }
+                packageChangeRepository
+                    .packageChanged(UserHandle.of(userId))
+                    .onStart { emit(PackageChangeModel.Empty) }
+                    .map { reloadComponents(userId, packageManager) }
+                    .distinctUntilChanged()
+                    .shareIn(backgroundScope, SharingStarted.WhileSubscribed(), replay = 1)
             }
-        return packageChangeRepository
-            .packageChanged(UserHandle.of(userId))
-            .onStart { emit(PackageChangeModel.Empty) }
-            .map { reloadComponents(userId, packageManager) }
-            .distinctUntilChanged()
-            .flowOn(backgroundDispatcher)
-    }
+        }
 
     @WorkerThread
     private fun reloadComponents(userId: Int, packageManager: PackageManager): Set<ComponentName> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddable.kt
new file mode 100644
index 0000000..2cebbe3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddable.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.qs.pipeline.domain.autoaddable
+
+import android.content.ComponentName
+import android.provider.Settings
+import com.android.systemui.accessibility.data.repository.AccessibilityQsShortcutsRepository
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.Objects
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+
+/**
+ * [A11yShortcutAutoAddable] will auto add/remove qs tile of the accessibility framework feature
+ * based on the user's choices in the Settings app.
+ *
+ * The a11y feature component is added to [Settings.Secure.ACCESSIBILITY_QS_TARGETS] when the user
+ * selects to use qs tile as a shortcut for the a11 feature in the Settings app. The accessibility
+ * feature component is removed from [Settings.Secure.ACCESSIBILITY_QS_TARGETS] when the user
+ * doesn't want to use qs tile as a shortcut for the a11y feature in the Settings app.
+ *
+ * [A11yShortcutAutoAddable] tracks a [Settings.Secure.ACCESSIBILITY_QS_TARGETS] and when its value
+ * changes, it will emit a [AutoAddSignal.Add] for the [spec] if the [componentName] is a substring
+ * of the value; it will emit a [AutoAddSignal.Remove] for the [spec] if the [componentName] is not
+ * a substring of the value.
+ */
+class A11yShortcutAutoAddable
+@AssistedInject
+constructor(
+    private val a11yQsShortcutsRepository: AccessibilityQsShortcutsRepository,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    @Assisted private val spec: TileSpec,
+    @Assisted private val componentName: ComponentName
+) : AutoAddable {
+
+    override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+        return a11yQsShortcutsRepository
+            .a11yQsShortcutTargets(userId)
+            .map { it.contains(componentName.flattenToString()) }
+            .filterNotNull()
+            .distinctUntilChanged()
+            .map { if (it) AutoAddSignal.Add(spec) else AutoAddSignal.Remove(spec) }
+            .flowOn(bgDispatcher)
+    }
+
+    override val autoAddTracking = AutoAddTracking.Always
+
+    override val description =
+        "A11yShortcutAutoAddableSetting: $spec:$componentName ($autoAddTracking)"
+
+    override fun equals(other: Any?): Boolean {
+        return other is A11yShortcutAutoAddable &&
+            spec == other.spec &&
+            componentName == other.componentName
+    }
+
+    override fun hashCode(): Int {
+        return Objects.hash(spec, componentName)
+    }
+
+    override fun toString(): String {
+        return description
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(spec: TileSpec, componentName: ComponentName): A11yShortcutAutoAddable
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt
new file mode 100644
index 0000000..08e3920
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.qs.pipeline.domain.autoaddable
+
+import android.view.accessibility.Flags
+import com.android.internal.accessibility.AccessibilityShortcutController
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.ColorCorrectionTile
+import com.android.systemui.qs.tiles.ColorInversionTile
+import com.android.systemui.qs.tiles.OneHandedModeTile
+import com.android.systemui.qs.tiles.ReduceBrightColorsTile
+
+object A11yShortcutAutoAddableList {
+
+    /**
+     * Generate a collection of [A11yShortcutAutoAddable] for the framework tiles related to
+     * accessibility features with shortcut options
+     */
+    fun getA11yShortcutAutoAddables(factory: A11yShortcutAutoAddable.Factory): Set<AutoAddable> {
+        return if (Flags.a11yQsShortcut()) {
+            setOf(
+                factory.create(
+                    TileSpec.create(ColorCorrectionTile.TILE_SPEC),
+                    AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME
+                ),
+                factory.create(
+                    TileSpec.create(ColorInversionTile.TILE_SPEC),
+                    AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME
+                ),
+                factory.create(
+                    TileSpec.create(OneHandedModeTile.TILE_SPEC),
+                    AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME
+                ),
+                factory.create(
+                    TileSpec.create(ReduceBrightColorsTile.TILE_SPEC),
+                    AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME
+                ),
+            )
+        } else {
+            emptySet()
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index d04e4f5..53f287b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -33,11 +33,13 @@
 import android.widget.ImageView;
 import android.widget.ImageView.ScaleType;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.settingslib.Utils;
-import com.android.systemui.res.R;
 import com.android.systemui.plugins.qs.QSIconView;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.qs.QSTile.State;
+import com.android.systemui.res.R;
 
 import java.util.Objects;
 
@@ -52,7 +54,10 @@
     private boolean mDisabledByPolicy = false;
     private int mTint;
     @Nullable
-    private QSTile.Icon mLastIcon;
+    @VisibleForTesting
+    QSTile.Icon mLastIcon;
+
+    private boolean mIconChangeScheduled;
 
     private ValueAnimator mColorAnimator = new ValueAnimator();
 
@@ -112,6 +117,7 @@
     }
 
     protected void updateIcon(ImageView iv, State state, boolean allowAnimations) {
+        mIconChangeScheduled = false;
         final QSTile.Icon icon = state.iconSupplier != null ? state.iconSupplier.get() : state.icon;
         if (!Objects.equals(icon, iv.getTag(R.id.qs_icon_tag))) {
             boolean shouldAnimate = allowAnimations && shouldAnimate(iv);
@@ -167,7 +173,12 @@
             mState = state.state;
             mDisabledByPolicy = state.disabledByPolicy;
             if (mTint != 0 && allowAnimations && shouldAnimate(iv)) {
-                animateGrayScale(mTint, color, iv, () -> updateIcon(iv, state, allowAnimations));
+                mIconChangeScheduled = true;
+                animateGrayScale(mTint, color, iv, () -> {
+                    if (mIconChangeScheduled) {
+                        updateIcon(iv, state, allowAnimations);
+                    }
+                });
             } else {
                 setTint(iv, color);
                 updateIcon(iv, state, allowAnimations);
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 216d716..88863cb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.qs.tiles
 
 import android.app.AlertDialog
+import android.app.BroadcastOptions
+import android.app.PendingIntent
 import android.content.Intent
 import android.os.Handler
 import android.os.Looper
@@ -42,6 +44,8 @@
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.recordissue.RecordIssueDialogDelegate
 import com.android.systemui.res.R
+import com.android.systemui.screenrecord.RecordingService
+import com.android.systemui.settings.UserContextProvider
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import javax.inject.Inject
@@ -61,6 +65,7 @@
     private val keyguardDismissUtil: KeyguardDismissUtil,
     private val keyguardStateController: KeyguardStateController,
     private val dialogLaunchAnimator: DialogLaunchAnimator,
+    private val userContextProvider: UserContextProvider,
     private val delegateFactory: RecordIssueDialogDelegate.Factory,
 ) :
     QSTileImpl<QSTile.BooleanState>(
@@ -91,12 +96,22 @@
     public override fun handleClick(view: View?) {
         if (isRecording) {
             isRecording = false
+            stopScreenRecord()
         } else {
             mUiHandler.post { showPrompt(view) }
         }
         refreshState()
     }
 
+    private fun stopScreenRecord() =
+        PendingIntent.getService(
+                userContextProvider.userContext,
+                RecordingService.REQUEST_CODE,
+                RecordingService.getStopIntent(userContextProvider.userContext),
+                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+            )
+            .send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle())
+
     private fun showPrompt(view: View?) {
         val dialog: AlertDialog =
             delegateFactory
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 592cb3b..211b4594 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -192,6 +192,7 @@
     private DialogLaunchAnimator mDialogLaunchAnimator;
     private boolean mHasWifiEntries;
     private WifiStateWorker mWifiStateWorker;
+    private boolean mHasActiveSubId;
 
     @VisibleForTesting
     static final float TOAST_PARAMS_HORIZONTAL_WEIGHT = 1.0f;
@@ -299,6 +300,7 @@
                 mExecutor);
         // Listen the subscription changes
         mOnSubscriptionsChangedListener = new InternetOnSubscriptionChangedListener();
+        refreshHasActiveSubId();
         mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor,
                 mOnSubscriptionsChangedListener);
         mDefaultDataSubId = getDefaultDataSubscriptionId();
@@ -901,18 +903,22 @@
      * @return whether there is the carrier item in the slice.
      */
     boolean hasActiveSubId() {
-        if (mSubscriptionManager == null) {
-            if (DEBUG) {
-                Log.d(TAG, "SubscriptionManager is null, can not check carrier.");
-            }
+        if (isAirplaneModeEnabled() || mTelephonyManager == null) {
             return false;
         }
 
-        if (isAirplaneModeEnabled() || mTelephonyManager == null
-                || mSubscriptionManager.getActiveSubscriptionIdList().length <= 0) {
-            return false;
+        return mHasActiveSubId;
+    }
+
+    private void refreshHasActiveSubId() {
+        if (mSubscriptionManager == null) {
+            mHasActiveSubId = false;
+            Log.e(TAG, "SubscriptionManager is null, set mHasActiveSubId = false");
+            return;
         }
-        return true;
+
+        mHasActiveSubId = mSubscriptionManager.getActiveSubscriptionIdList().length > 0;
+        Log.i(TAG, "mHasActiveSubId:" + mHasActiveSubId);
     }
 
     /**
@@ -1204,6 +1210,7 @@
 
         @Override
         public void onSubscriptionsChanged() {
+            refreshHasActiveSubId();
             updateListener();
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index cc53aab..a9dd25b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -85,8 +85,8 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.KeyguardWmStateRefactor;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager;
 import com.android.systemui.model.SysUiState;
@@ -616,7 +616,7 @@
         mDisplayTracker = displayTracker;
         mUnfoldTransitionProgressForwarder = unfoldTransitionProgressForwarder;
 
-        if (!featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+        if (!KeyguardWmStateRefactor.isEnabled()) {
             mSysuiUnlockAnimationController = sysuiUnlockAnimationController;
         } else {
             mSysuiUnlockAnimationController = inWindowLauncherUnlockAnimationManager;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index c0ceba3..530c124 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -3576,11 +3576,6 @@
     }
 
     @Override
-    public NotificationStackScrollLayoutController getNotificationStackScrollLayoutController() {
-        return mNotificationStackScrollLayoutController;
-    }
-
-    @Override
     public void disableHeader(int state1, int state2, boolean animated) {
         mShadeHeaderController.disable(state1, state2, animated);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
index e54286f..4f970b3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
@@ -17,7 +17,6 @@
 
 import android.view.ViewPropertyAnimator
 import com.android.systemui.statusbar.GestureRecorder
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.statusbar.policy.HeadsUpManager
 
@@ -44,9 +43,6 @@
     /** Animates the view from its current alpha to zero then runs the runnable. */
     fun fadeOut(startDelayMs: Long, durationMs: Long, endAction: Runnable): ViewPropertyAnimator
 
-    /** Returns the NSSL controller. */
-    val notificationStackScrollLayoutController: NotificationStackScrollLayoutController
-
     /** Set whether the bouncer is showing. */
     fun setBouncerShowing(bouncerShowing: Boolean)
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index fc84973..28d4457 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -35,7 +35,6 @@
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.Looper;
 import android.provider.Settings;
 import android.telephony.CarrierConfigManager;
@@ -61,7 +60,6 @@
 import com.android.settingslib.mobile.TelephonyIcons;
 import com.android.settingslib.net.DataUsageController;
 import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -73,6 +71,7 @@
 import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.log.dagger.StatusBarNetworkControllerLog;
 import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -85,6 +84,8 @@
 
 import dalvik.annotation.optimization.NeverCompile;
 
+import kotlin.Unit;
+
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -99,8 +100,6 @@
 
 import javax.inject.Inject;
 
-import kotlin.Unit;
-
 /** Platform implementation of the network controller. **/
 @SysUISingleton
 public class NetworkControllerImpl extends BroadcastReceiver
@@ -350,7 +349,7 @@
         // AIRPLANE_MODE_CHANGED is sent at boot; we've probably already missed it
         updateAirplaneMode(true /* force callback */);
         mUserTracker = userTracker;
-        mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mMainHandler));
+        mUserTracker.addCallback(mUserChangedCallback, mBgExecutor);
 
         deviceProvisionedController.addCallback(new DeviceProvisionedListener() {
             @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
index 6e3b15d..c643238 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -52,8 +52,8 @@
             entry: NotificationEntry,
             recoveredBuilder: Notification.Builder,
             logger: NotificationContentInflaterLogger
-    ) {
-        val messagingStyle = recoveredBuilder.style as? Notification.MessagingStyle ?: return
+    ): Notification.MessagingStyle? {
+        val messagingStyle = recoveredBuilder.style as? Notification.MessagingStyle ?: return null
         messagingStyle.conversationType =
                 if (entry.ranking.channel.isImportantConversation)
                     Notification.MessagingStyle.CONVERSATION_TYPE_IMPORTANT
@@ -68,6 +68,7 @@
         }
         messagingStyle.unreadMessageCount =
                 conversationNotificationManager.getUnreadCount(entry, recoveredBuilder)
+        return messagingStyle
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index 73decfc..639e23a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -362,8 +362,12 @@
     }
 
     NotifInflater.Params getInflaterParams(NotifUiAdjustment adjustment, String reason) {
-        return new NotifInflater.Params(adjustment.isMinimized(), reason,
-                adjustment.isSnoozeEnabled());
+        return new NotifInflater.Params(
+                adjustment.isMinimized(),
+                reason,
+                adjustment.isSnoozeEnabled(),
+                adjustment.isChildInGroup()
+        );
     }
 
     private void abortInflation(NotificationEntry entry, String reason) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt
index 4483599..c0b187b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt
@@ -20,9 +20,9 @@
 import com.android.systemui.statusbar.notification.collection.render.NotifViewController
 
 /**
- * Used by the [PreparationCoordinator].  When notifications are added or updated, the
- * NotifInflater is asked to (re)inflated and prepare their views.  This inflation occurs off the
- * main thread. When the inflation is finished, NotifInflater will trigger its InflationCallback.
+ * Used by the [PreparationCoordinator]. When notifications are added or updated, the NotifInflater
+ * is asked to (re)inflated and prepare their views. This inflation occurs off the main thread. When
+ * the inflation is finished, NotifInflater will trigger its InflationCallback.
  */
 interface NotifInflater {
     /**
@@ -33,7 +33,7 @@
     fun rebindViews(entry: NotificationEntry, params: Params, callback: InflationCallback)
 
     /**
-     * Called to inflate the views of an entry.  Views are not considered inflated until all of its
+     * Called to inflate the views of an entry. Views are not considered inflated until all of its
      * views are bound. Once all views are inflated, the InflationCallback is triggered.
      *
      * @param callback callback called after inflation finishes
@@ -41,25 +41,24 @@
     fun inflateViews(entry: NotificationEntry, params: Params, callback: InflationCallback)
 
     /**
-     * Request to stop the inflation of an entry.  For example, called when a notification is
-     * removed and no longer needs to be inflated.  Returns whether anything may have been aborted.
+     * Request to stop the inflation of an entry. For example, called when a notification is removed
+     * and no longer needs to be inflated. Returns whether anything may have been aborted.
      */
     fun abortInflation(entry: NotificationEntry): Boolean
 
-    /**
-     * Called to let the system remove the content views from the notification row.
-     */
+    /** Called to let the system remove the content views from the notification row. */
     fun releaseViews(entry: NotificationEntry)
 
-    /**
-     * Callback once all the views are inflated and bound for a given NotificationEntry.
-     */
+    /** Callback once all the views are inflated and bound for a given NotificationEntry. */
     interface InflationCallback {
         fun onInflationFinished(entry: NotificationEntry, controller: NotifViewController)
     }
 
-    /**
-     * A class holding parameters used when inflating the notification row
-     */
-    class Params(val isLowPriority: Boolean, val reason: String, val showSnooze: Boolean)
+    /** A class holding parameters used when inflating the notification row */
+    class Params(
+        val isLowPriority: Boolean,
+        val reason: String,
+        val showSnooze: Boolean,
+        val isChildInGroup: Boolean = false,
+    )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt
index ee0b008..e1d2cdc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt
@@ -20,6 +20,7 @@
 import android.app.RemoteInput
 import android.graphics.drawable.Icon
 import android.text.TextUtils
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
 
 /**
  * An immutable object which contains minimal state extracted from an entry that represents state
@@ -34,6 +35,7 @@
     val isSnoozeEnabled: Boolean,
     val isMinimized: Boolean,
     val needsRedaction: Boolean,
+    val isChildInGroup: Boolean,
 ) {
     companion object {
         @JvmStatic
@@ -48,6 +50,11 @@
             oldAdjustment.needsRedaction != newAdjustment.needsRedaction -> true
             areDifferent(oldAdjustment.smartActions, newAdjustment.smartActions) -> true
             newAdjustment.smartReplies != oldAdjustment.smartReplies -> true
+            // TODO(b/217799515): Here we decide whether to re-inflate the row on every group-status
+            //  change if we want to keep the single-line view, the following line should be:
+            //  !oldAdjustment.isChildInGroup && newAdjustment.isChildInGroup -> true
+            AsyncHybridViewInflation.isEnabled &&
+                    oldAdjustment.isChildInGroup != newAdjustment.isChildInGroup -> true
             else -> false
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
index 0585456..6f44c13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
 import com.android.systemui.util.ListenerSet
 import com.android.systemui.util.settings.SecureSettings
 import javax.inject.Inject
@@ -43,7 +44,8 @@
     private val secureSettings: SecureSettings,
     private val lockscreenUserManager: NotificationLockscreenUserManager,
     private val sectionStyleProvider: SectionStyleProvider,
-    private val userTracker: UserTracker
+    private val userTracker: UserTracker,
+    private val groupMembershipManager: GroupMembershipManager,
 ) {
     private val dirtyListeners = ListenerSet<Runnable>()
     private var isSnoozeSettingsEnabled = false
@@ -121,5 +123,6 @@
         isSnoozeEnabled = isSnoozeSettingsEnabled && !entry.isCanceled,
         isMinimized = isEntryMinimized(entry),
         needsRedaction = lockscreenUserManager.needsRedaction(entry),
+        isChildInGroup = groupMembershipManager.isChildInGroup(entry),
     )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index 80ef14b..cd816ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -20,6 +20,7 @@
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_SINGLE_LINE;
 
 import static java.util.Objects.requireNonNull;
 
@@ -49,6 +50,7 @@
 import com.android.systemui.statusbar.notification.row.RowContentBindStage;
 import com.android.systemui.statusbar.notification.row.RowInflaterTask;
 import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 
 import javax.inject.Inject;
@@ -127,6 +129,8 @@
             @NonNull NotifInflater.Params params,
             NotificationRowContentBinder.InflationCallback callback)
             throws InflationException {
+        //TODO(b/217799515): Remove the entry parameter from getViewParentForNotification(), this
+        // function returns the NotificationStackScrollLayout regardless of the entry.
         ViewGroup parent = mListContainer.getViewParentForNotification(entry);
 
         if (entry.rowExists()) {
@@ -174,6 +178,9 @@
         params.markContentViewsFreeable(FLAG_CONTENT_VIEW_CONTRACTED);
         params.markContentViewsFreeable(FLAG_CONTENT_VIEW_EXPANDED);
         params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC);
+        if (AsyncHybridViewInflation.isEnabled()) {
+            params.markContentViewsFreeable(FLAG_CONTENT_VIEW_SINGLE_LINE);
+        }
         mRowContentBindStage.requestRebind(entry, null);
     }
 
@@ -254,6 +261,16 @@
             params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC);
         }
 
+        if (AsyncHybridViewInflation.isEnabled()) {
+            if (inflaterParams.isChildInGroup()) {
+                params.requireContentViews(FLAG_CONTENT_VIEW_SINGLE_LINE);
+            } else {
+                // TODO(b/217799515): here we decide whether to free the single-line view
+                //  when the group status changes
+                params.markContentViewsFreeable(FLAG_CONTENT_VIEW_SINGLE_LINE);
+            }
+        }
+
         params.rebindAllContentViews();
         mLogger.logRequestingRebind(entry, inflaterParams);
         mRowContentBindStage.requestRebind(entry, en -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
index 61e6f65..8021d8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
@@ -127,6 +127,9 @@
         }
     }
 
+    /**
+     * Attach the Child Nodes to the parentNode using the structure from specMap
+     */
     private fun attachChildren(
         parentNode: ShadeNode,
         specMap: Map<NodeController, NodeSpec>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
index aca8b64..342828c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
@@ -6,6 +6,7 @@
 import android.net.Uri
 import android.os.Handler
 import android.os.HandlerExecutor
+import android.os.HandlerThread
 import android.os.UserHandle
 import android.provider.Settings
 import com.android.keyguard.KeyguardUpdateMonitor
@@ -87,6 +88,7 @@
             secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS)
     private val onStateChangedListeners = ListenerSet<Consumer<String>>()
     private var hideSilentNotificationsOnLockscreen: Boolean = false
+    private val handlerThread: HandlerThread = HandlerThread("KeyguardNotificationVis")
 
     private val userTrackerCallback = object : UserTracker.Callback {
         override fun onUserChanged(newUser: Int, userContext: Context) {
@@ -154,7 +156,9 @@
                 notifyStateChanged("onStatusBarUpcomingStateChanged")
             }
         })
-        userTracker.addCallback(userTrackerCallback, HandlerExecutor(handler))
+        handlerThread.start()
+        userTracker.addCallback(userTrackerCallback,
+                   HandlerExecutor(handlerThread.getThreadHandler()))
     }
 
     override fun addOnStateChangedListener(listener: Consumer<String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java
index d626c18..8ae324f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java
@@ -44,6 +44,7 @@
     /**
      * Execute the stage asynchronously.
      *
+     * @param entry the NotificationEntry to bind
      * @param row notification top-level view to bind views to
      * @param callback callback after stage finishes
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
index 43d99a0..6bc2b2f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
@@ -16,19 +16,27 @@
 
 package com.android.systemui.statusbar.notification.row;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.graphics.drawable.Icon;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.View;
+import android.view.ViewStub;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.ConversationLayout;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.NotificationFadeAware;
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.ConversationAvatar;
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.FacePile;
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleIcon;
 
 /**
  * A hybrid view which may contain information about one ore more conversations.
@@ -37,6 +45,7 @@
 
     private ImageView mConversationIconView;
     private TextView mConversationSenderName;
+    private ViewStub mConversationFacePileStub;
     private View mConversationFacePile;
     private int mSingleAvatarSize;
     private int mFacePileSize;
@@ -65,7 +74,16 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mConversationIconView = requireViewById(com.android.internal.R.id.conversation_icon);
-        mConversationFacePile = requireViewById(com.android.internal.R.id.conversation_face_pile);
+        if (AsyncHybridViewInflation.isEnabled()) {
+            mConversationFacePileStub =
+                    requireViewById(com.android.internal.R.id.conversation_face_pile);
+        } else {
+            // TODO(b/217799515): This usage is vague because mConversationFacePile represents both
+            //  View and ViewStub at different stages of View inflation, should be removed when
+            //  AsyncHybridViewInflation flag is removed
+            mConversationFacePile =
+                    requireViewById(com.android.internal.R.id.conversation_face_pile);
+        }
         mConversationSenderName = requireViewById(R.id.conversation_notification_sender);
         applyTextColor(mConversationSenderName, mSecondaryTextColor);
         mFacePileSize = getResources()
@@ -85,7 +103,8 @@
 
     @Override
     public void bind(@Nullable CharSequence title, @Nullable CharSequence text,
-            @Nullable View contentView) {
+                     @Nullable View contentView) {
+        AsyncHybridViewInflation.assertInLegacyMode();
         if (!(contentView instanceof ConversationLayout)) {
             super.bind(title, text, contentView);
             return;
@@ -137,6 +156,77 @@
         super.bind(conversationTitle, conversationText, conversationLayout);
     }
 
+    /**
+     * Set the avatar using ConversationAvatar from SingleLineViewModel
+     *
+     * @param conversationAvatar the icon needed for a single-line conversation view, it should be
+     *                           either an instance of SingleIcon or FacePile
+     */
+    public void setAvatar(@NonNull ConversationAvatar conversationAvatar) {
+        if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return;
+        if (conversationAvatar instanceof SingleIcon) {
+            SingleIcon avatar = (SingleIcon) conversationAvatar;
+            if (mConversationFacePile != null) mConversationFacePile.setVisibility(GONE);
+            mConversationIconView.setVisibility(VISIBLE);
+            mConversationIconView.setImageDrawable(avatar.getIconDrawable());
+            setSize(mConversationIconView, mSingleAvatarSize);
+            return;
+        }
+
+        // If conversationAvatar is not a SingleIcon, it should be a FacePile.
+        // Bind the face pile with it.
+        FacePile facePileModel = (FacePile) conversationAvatar;
+        mConversationIconView.setVisibility(GONE);
+        // Inflate mConversationFacePile from ViewStub
+        if (mConversationFacePile == null) {
+            mConversationFacePile = mConversationFacePileStub.inflate();
+        }
+        mConversationFacePile.setVisibility(VISIBLE);
+
+        ImageView facePileBottomBg = mConversationFacePile.requireViewById(
+                com.android.internal.R.id.conversation_face_pile_bottom_background);
+        ImageView facePileBottom = mConversationFacePile.requireViewById(
+                com.android.internal.R.id.conversation_face_pile_bottom);
+        ImageView facePileTop = mConversationFacePile.requireViewById(
+                com.android.internal.R.id.conversation_face_pile_top);
+
+        int bottomBackgroundColor = facePileModel.getBottomBackgroundColor();
+        facePileBottomBg.setImageTintList(ColorStateList.valueOf(bottomBackgroundColor));
+
+        facePileBottom.setImageDrawable(facePileModel.getBottomIconDrawable());
+        facePileTop.setImageDrawable(facePileModel.getTopIconDrawable());
+
+        setSize(mConversationFacePile, mFacePileSize);
+        setSize(facePileBottom, mFacePileAvatarSize);
+        setSize(facePileTop, mFacePileAvatarSize);
+        setSize(facePileBottomBg, mFacePileAvatarSize + 2 * mFacePileProtectionWidth);
+
+        mTransformationHelper.addViewTransformingToSimilar(facePileTop);
+        mTransformationHelper.addViewTransformingToSimilar(facePileBottom);
+        mTransformationHelper.addViewTransformingToSimilar(facePileBottomBg);
+
+    }
+
+    /**
+     * bind the text views
+     */
+    public void setText(
+            CharSequence titleText,
+            CharSequence contentText,
+            CharSequence conversationSenderName
+    ) {
+        if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return;
+        if (conversationSenderName == null) {
+            mConversationSenderName.setVisibility(GONE);
+        } else {
+            mConversationSenderName.setVisibility(VISIBLE);
+            mConversationSenderName.setText(conversationSenderName);
+        }
+        // TODO (b/217799515): super.bind() doesn't use contentView, remove the contentView
+        //  argument when the flag is removed
+        super.bind(/* title = */ titleText, /* text = */ contentText, /* contentView = */ null);
+    }
+
     private static void setSize(View view, int size) {
         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) view.getLayoutParams();
         lp.width = size;
@@ -153,4 +243,9 @@
         super.setNotificationFaded(faded);
         NotificationFadeAware.setLayerTypeForFaded(mConversationFacePile, faded);
     }
+
+    @VisibleForTesting
+    TextView getConversationSenderNameView() {
+        return mConversationSenderName;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
index ddd9bdd..09c0349 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
@@ -32,6 +32,7 @@
 
 import com.android.internal.widget.ConversationLayout;
 import com.android.systemui.res.R;
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
 
 /**
  * A class managing hybrid groups that include {@link HybridNotificationView} and the notification
@@ -41,6 +42,8 @@
 
     private final Context mContext;
 
+    private static final String TAG = "HybridGroupManager";
+
     private float mOverflowNumberSize;
     private int mOverflowNumberPadding;
 
@@ -93,21 +96,34 @@
     public HybridNotificationView bindFromNotification(HybridNotificationView reusableView,
             View contentView, StatusBarNotification notification,
             ViewGroup parent) {
+        AsyncHybridViewInflation.assertInLegacyMode();
         boolean isNewView = false;
         if (reusableView == null) {
             Trace.beginSection("HybridGroupManager#bindFromNotification");
             reusableView = inflateHybridView(contentView, parent);
             isNewView = true;
         }
-        CharSequence titleText = resolveTitle(notification.getNotification());
-        CharSequence contentText = resolveText(notification.getNotification());
-        reusableView.bind(titleText, contentText, contentView);
+
+        updateReusableView(reusableView, notification, contentView);
         if (isNewView) {
             Trace.endSection();
         }
         return reusableView;
     }
 
+    /**
+     * Update the HybridNotificationView (single-line view)'s appearance
+     */
+    public void updateReusableView(HybridNotificationView reusableView,
+            StatusBarNotification notification, View contentView) {
+        AsyncHybridViewInflation.assertInLegacyMode();
+        final CharSequence titleText = resolveTitle(notification.getNotification());
+        final CharSequence contentText = resolveText(notification.getNotification());
+        if (reusableView != null) {
+            reusableView.bind(titleText, contentText, contentView);
+        }
+    }
+
     @Nullable
     public static CharSequence resolveText(Notification notification) {
         CharSequence contentText = notification.extras.getCharSequence(Notification.EXTRA_TEXT);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt
index d10b556..8bc8e8c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt
@@ -22,11 +22,9 @@
 import android.view.LayoutInflater
 import android.view.View
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
-import com.android.systemui.statusbar.notification.row.NotificationRowModule.NOTIF_REMOTEVIEWS_FACTORIES
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
-import javax.inject.Named
 
 /**
  * Implementation of [NotifLayoutInflaterFactory]. This class uses a set of
@@ -37,8 +35,7 @@
 constructor(
     @Assisted private val row: ExpandableNotificationRow,
     @Assisted @InflationFlag val layoutType: Int,
-    @Named(NOTIF_REMOTEVIEWS_FACTORIES)
-    private val remoteViewsFactories: Set<@JvmSuppressWildcards NotifRemoteViewsFactory>
+    private val notifRemoteViewsFactoryContainer: NotifRemoteViewsFactoryContainer
 ) : LayoutInflater.Factory2 {
 
     override fun onCreateView(
@@ -49,7 +46,7 @@
     ): View? {
         var handledFactory: NotifRemoteViewsFactory? = null
         var result: View? = null
-        for (layoutFactory in remoteViewsFactories) {
+        for (layoutFactory in notifRemoteViewsFactoryContainer.factories) {
             layoutFactory.instantiate(row, layoutType, parent, name, context, attrs)?.run {
                 check(handledFactory == null) {
                     "$layoutFactory tries to produce name:$name with type:$layoutType. " +
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
new file mode 100644
index 0000000..99177c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
+
+interface NotifRemoteViewsFactoryContainer {
+    val factories: Set<NotifRemoteViewsFactory>
+}
+
+class NotifRemoteViewsFactoryContainerImpl
+@Inject
+constructor(
+    featureFlags: FeatureFlags,
+    precomputedTextViewFactory: PrecomputedTextViewFactory,
+    bigPictureLayoutInflaterFactory: BigPictureLayoutInflaterFactory,
+    callLayoutSetDataAsyncFactory: CallLayoutSetDataAsyncFactory,
+) : NotifRemoteViewsFactoryContainer {
+    override val factories: Set<NotifRemoteViewsFactory> = buildSet {
+        add(precomputedTextViewFactory)
+        if (featureFlags.isEnabled(Flags.BIGPICTURE_NOTIFICATION_LAZY_LOADING)) {
+            add(bigPictureLayoutInflaterFactory)
+        }
+        if (featureFlags.isEnabled(Flags.CALL_LAYOUT_ASYNC_SET_DATA)) {
+            add(callLayoutSetDataAsyncFactory)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index f186e66..913d5f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -20,6 +20,7 @@
 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED;
 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED;
 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
+import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_SINGLELINE;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -41,15 +42,19 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.ImageMessageConsumer;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.media.controls.util.MediaFeatureFlag;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.InflationTask;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
 import com.android.systemui.statusbar.notification.InflationException;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
+import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineConversationViewBinder;
+import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineViewBinder;
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleLineViewModel;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
@@ -135,7 +140,7 @@
         AsyncInflationTask task = new AsyncInflationTask(
                 mBgExecutor,
                 mInflateSynchronously,
-                contentToBind,
+                /* reInflateFlags = */ contentToBind,
                 mRemoteViewCache,
                 entry,
                 mConversationProcessor,
@@ -145,7 +150,7 @@
                 bindParams.usesIncreasedHeadsUpHeight,
                 callback,
                 mRemoteInputManager.getRemoteViewsOnClickHandler(),
-                mIsMediaInQS,
+                /* isMediaFlagEnabled = */ mIsMediaInQS,
                 mSmartReplyStateInflater,
                 mNotifLayoutInflaterFactoryProvider,
                 mLogger);
@@ -178,6 +183,29 @@
 
         result = inflateSmartReplyViews(result, reInflateFlags, entry, row.getContext(),
                 packageContext, row.getExistingSmartReplyState(), smartRepliesInflater, mLogger);
+        if (AsyncHybridViewInflation.isEnabled()) {
+            boolean isConversation = entry.getRanking().isConversation();
+            Notification.MessagingStyle messagingStyle = null;
+            if (isConversation) {
+                messagingStyle = mConversationProcessor
+                        .processNotification(entry, builder, mLogger);
+            }
+            result.mInflatedSingleLineViewModel = SingleLineViewInflater
+                    .inflateSingleLineViewModel(
+                            entry.getSbn().getNotification(),
+                            messagingStyle,
+                            builder,
+                            row.getContext()
+                    );
+            result.mInflatedSingleLineViewHolder =
+                    SingleLineViewInflater.inflateSingleLineViewHolder(
+                            isConversation,
+                            reInflateFlags,
+                            entry,
+                            row.getContext(),
+                            mLogger
+                    );
+        }
 
         apply(
                 mBgExecutor,
@@ -255,6 +283,15 @@
                     mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC);
                 });
                 break;
+            case FLAG_CONTENT_VIEW_SINGLE_LINE: {
+                if (AsyncHybridViewInflation.isEnabled()) {
+                    row.getPrivateLayout().performWhenContentInactive(
+                            VISIBLE_TYPE_SINGLELINE,
+                            () -> row.getPrivateLayout().setSingleLineView(null)
+                    );
+                }
+                break;
+            }
             default:
                 break;
         }
@@ -282,6 +319,10 @@
         if ((contentViews & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
             row.getPublicLayout().removeContentInactiveRunnable(VISIBLE_TYPE_CONTRACTED);
         }
+        if (AsyncHybridViewInflation.isEnabled()
+                && (contentViews & FLAG_CONTENT_VIEW_SINGLE_LINE) != 0) {
+            row.getPrivateLayout().removeContentInactiveRunnable(VISIBLE_TYPE_SINGLELINE);
+        }
     }
 
     private static InflationProgress inflateSmartReplyViews(
@@ -772,6 +813,25 @@
                 }
                 setRepliesAndActions = true;
             }
+
+            if (AsyncHybridViewInflation.isEnabled()
+                    && (reInflateFlags & FLAG_CONTENT_VIEW_SINGLE_LINE) != 0) {
+                HybridNotificationView viewHolder = result.mInflatedSingleLineViewHolder;
+                SingleLineViewModel viewModel = result.mInflatedSingleLineViewModel;
+                if (viewHolder != null && viewModel != null) {
+                    if (viewModel.isConversation()) {
+                        SingleLineConversationViewBinder.bind(
+                                result.mInflatedSingleLineViewModel,
+                                result.mInflatedSingleLineViewHolder
+                        );
+                    } else {
+                        SingleLineViewBinder.bind(result.mInflatedSingleLineViewModel,
+                                result.mInflatedSingleLineViewHolder);
+                    }
+                    privateLayout.setSingleLineView(result.mInflatedSingleLineViewHolder);
+                }
+            }
+
             if (setRepliesAndActions) {
                 privateLayout.setInflatedSmartReplyState(result.inflatedSmartReplyState);
             }
@@ -941,19 +1001,23 @@
                     // For all of our templates, we want it to be RTL
                     packageContext = new RtlEnabledContext(packageContext);
                 }
-                if (mEntry.getRanking().isConversation()) {
-                    mConversationProcessor.processNotification(mEntry, recoveredBuilder, mLogger);
+                boolean isConversation = mEntry.getRanking().isConversation();
+                Notification.MessagingStyle messagingStyle = null;
+                if (isConversation) {
+                    messagingStyle = mConversationProcessor.processNotification(
+                            mEntry, recoveredBuilder, mLogger);
                 }
                 InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
                         recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight,
                         mUsesIncreasedHeadsUpHeight, packageContext, mRow,
                         mNotifLayoutInflaterFactoryProvider, mLogger);
+
                 mLogger.logAsyncTaskProgress(mEntry,
                         "getting existing smart reply state (on wrong thread!)");
                 InflatedSmartReplyState previousSmartReplyState = mRow.getExistingSmartReplyState();
                 mLogger.logAsyncTaskProgress(mEntry, "inflating smart reply views");
                 InflationProgress result = inflateSmartReplyViews(
-                        inflationProgress,
+                        /* result = */ inflationProgress,
                         mReInflateFlags,
                         mEntry,
                         mContext,
@@ -962,6 +1026,27 @@
                         mSmartRepliesInflater,
                         mLogger);
 
+                if (AsyncHybridViewInflation.isEnabled()) {
+                    // Inflate the single-line content view's ViewModel and ViewHolder from the
+                    // background thread, the ViewHolder needs to be bind with ViewModel later from
+                    // the main thread.
+                    result.mInflatedSingleLineViewModel = SingleLineViewInflater
+                            .inflateSingleLineViewModel(
+                                    mEntry.getSbn().getNotification(),
+                                    messagingStyle,
+                                    recoveredBuilder,
+                                    mContext
+                            );
+                    result.mInflatedSingleLineViewHolder =
+                            SingleLineViewInflater.inflateSingleLineViewHolder(
+                                    isConversation,
+                                    mReInflateFlags,
+                                    mEntry,
+                                    mContext,
+                                    mLogger
+                            );
+                }
+
                 mLogger.logAsyncTaskProgress(mEntry,
                         "getting row image resolver (on wrong thread!)");
                 final NotificationInlineImageResolver imageResolver = mRow.getImageResolver();
@@ -1078,6 +1163,11 @@
         private InflatedSmartReplyState inflatedSmartReplyState;
         private InflatedSmartReplyViewHolder expandedInflatedSmartReplies;
         private InflatedSmartReplyViewHolder headsUpInflatedSmartReplies;
+
+        // ViewModel for SingleLineView, holds the UI State
+        SingleLineViewModel mInflatedSingleLineViewModel;
+        // Inflated SingleLineViewHolder, SingleLineView that lacks the UI State
+        HybridNotificationView mInflatedSingleLineViewHolder;
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterLogger.kt
index 4f5455d..ee9462c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterLogger.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_SINGLE_LINE
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
 import javax.inject.Inject
 
@@ -99,6 +100,26 @@
         )
     }
 
+    fun logInflateSingleLine(
+        entry: NotificationEntry,
+        @InflationFlag inflationFlags: Int,
+        isConversation: Boolean
+    ) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = entry.logKey
+                int1 = inflationFlags
+                bool1 = isConversation
+            },
+            {
+                "inflateSingleLineView, inflationFlags: ${flagToString(int1)} for $str1, " +
+                    "isConversation: $bool1"
+            }
+        )
+    }
+
     companion object {
         fun flagToString(@InflationFlag flag: Int): String {
             if (flag == 0) {
@@ -121,6 +142,9 @@
             if (flag and FLAG_CONTENT_VIEW_PUBLIC != 0) {
                 l.add("PUBLIC")
             }
+            if (flag and FLAG_CONTENT_VIEW_SINGLE_LINE != 0) {
+                l.add("SINGLE_LINE")
+            }
             return l.joinToString("|")
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index a1718b9..402ea51 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -57,6 +57,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationCustomViewWrapper;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
 import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
@@ -86,7 +87,7 @@
     public static final int VISIBLE_TYPE_CONTRACTED = 0;
     public static final int VISIBLE_TYPE_EXPANDED = 1;
     public static final int VISIBLE_TYPE_HEADSUP = 2;
-    private static final int VISIBLE_TYPE_SINGLELINE = 3;
+    public static final int VISIBLE_TYPE_SINGLELINE = 3;
     /**
      * Used when there is no content on the view such as when we're a public layout but don't
      * need to show.
@@ -98,6 +99,7 @@
     private final Rect mClipBounds = new Rect();
 
     private int mMinContractedHeight;
+    private int mMinSingleLineHeight;
     private View mContractedChild;
     private View mExpandedChild;
     private View mHeadsUpChild;
@@ -234,6 +236,11 @@
     public void reinflate() {
         mMinContractedHeight = getResources().getDimensionPixelSize(
                 R.dimen.min_notification_layout_height);
+        if (AsyncHybridViewInflation.isEnabled()) {
+            //TODO: set the height with a more reasonable min single-line height
+            mMinSingleLineHeight = getResources().getDimensionPixelSize(
+                    R.dimen.conversation_single_line_face_pile_size);
+        }
     }
 
     public void setHeights(int smallHeight, int headsUpMaxHeight, int maxHeight) {
@@ -540,6 +547,28 @@
         updateShownWrapper(mVisibleType);
     }
 
+    /**
+     * Sets the single-line view. Child may be null to remove the view.
+     * @param child single-line content view to set
+     */
+    public void setSingleLineView(@Nullable HybridNotificationView child) {
+        if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return;
+        if (mSingleLineView != null) {
+            mOnContentViewInactiveListeners.remove(mSingleLineView);
+            mSingleLineView.animate().cancel();
+            removeView(mSingleLineView);
+        }
+        if (child == null) {
+            mSingleLineView = null;
+            if (mTransformationStartVisibleType == VISIBLE_TYPE_SINGLELINE) {
+                mTransformationStartVisibleType = VISIBLE_TYPE_NONE;
+            }
+            return;
+        }
+        addView(child);
+        mSingleLineView = child;
+    }
+
     @Override
     public void onViewAdded(View child) {
         super.onViewAdded(child);
@@ -809,7 +838,17 @@
             return mContractedChild != null
                     ? getViewHeight(VISIBLE_TYPE_CONTRACTED) : mMinContractedHeight;
         } else {
-            return mSingleLineView.getHeight();
+            if (AsyncHybridViewInflation.isEnabled()) {
+                if (mSingleLineView != null) {
+                    return getViewHeight(VISIBLE_TYPE_SINGLELINE);
+                } else {
+                    Log.wtf(TAG, "getMinHeight: mSingleLineView == null");
+                    return mMinSingleLineHeight;
+                }
+            } else {
+                AsyncHybridViewInflation.assertInLegacyMode();
+                return mSingleLineView.getHeight();
+            }
         }
     }
 
@@ -1264,19 +1303,30 @@
     }
 
     private void updateSingleLineView() {
-        if (mIsChildInGroup) {
+        try {
             Trace.beginSection("NotifContentView#updateSingleLineView");
-            boolean isNewView = mSingleLineView == null;
-            mSingleLineView = mHybridGroupManager.bindFromNotification(
-                    mSingleLineView, mContractedChild, mNotificationEntry.getSbn(), this);
-            if (isNewView) {
-                updateViewVisibility(mVisibleType, VISIBLE_TYPE_SINGLELINE,
-                        mSingleLineView, mSingleLineView);
+            if (AsyncHybridViewInflation.isEnabled()) {
+                return;
             }
+            AsyncHybridViewInflation.assertInLegacyMode();
+            if (mIsChildInGroup) {
+                boolean isNewView = mSingleLineView == null;
+                mSingleLineView = mHybridGroupManager.bindFromNotification(
+                        /* reusableView = */ mSingleLineView,
+                        /* contentView = */ mContractedChild,
+                        /* notification = */ mNotificationEntry.getSbn(),
+                        /* parent = */ this
+                );
+                if (isNewView && mSingleLineView != null) {
+                    updateViewVisibility(mVisibleType, VISIBLE_TYPE_SINGLELINE,
+                            mSingleLineView, mSingleLineView);
+                }
+            } else if (mSingleLineView != null) {
+                removeView(mSingleLineView);
+                mSingleLineView = null;
+            }
+        } finally {
             Trace.endSection();
-        } else if (mSingleLineView != null) {
-            removeView(mSingleLineView);
-            mSingleLineView = null;
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
index d7b7aa2..736140c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
@@ -80,6 +80,7 @@
                     FLAG_CONTENT_VIEW_EXPANDED,
                     FLAG_CONTENT_VIEW_HEADS_UP,
                     FLAG_CONTENT_VIEW_PUBLIC,
+                    FLAG_CONTENT_VIEW_SINGLE_LINE,
                     FLAG_CONTENT_VIEW_ALL})
     @interface InflationFlag {}
     /**
@@ -102,7 +103,12 @@
      */
     int FLAG_CONTENT_VIEW_PUBLIC = 1 << 3;
 
-    int FLAG_CONTENT_VIEW_ALL = (1 << 4) - 1;
+    /**
+     * The single line notification view. Show when the notification is shown as a child in group.
+     */
+    int FLAG_CONTENT_VIEW_SINGLE_LINE = 1 << 4;
+
+    int FLAG_CONTENT_VIEW_ALL = (1 << 5) - 1;
 
     /**
      * Parameters for content view binding
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
index 46ddba4..200a08a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
@@ -17,26 +17,15 @@
 package com.android.systemui.statusbar.notification.row;
 
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 
 import dagger.Binds;
 import dagger.Module;
-import dagger.Provides;
-import dagger.multibindings.ElementsIntoSet;
-
-import java.util.HashSet;
-import java.util.Set;
-
-import javax.inject.Named;
 
 /**
  * Dagger Module containing notification row and view inflation implementations.
  */
 @Module
 public abstract class NotificationRowModule {
-    public static final String NOTIF_REMOTEVIEWS_FACTORIES =
-            "notif_remoteviews_factories";
 
     /**
      * Provides notification row content binder instance.
@@ -54,24 +43,11 @@
     public abstract NotifRemoteViewCache provideNotifRemoteViewCache(
             NotifRemoteViewCacheImpl cacheImpl);
 
-    /** Provides view factories to be inflated in notification content. */
-    @Provides
-    @ElementsIntoSet
-    @Named(NOTIF_REMOTEVIEWS_FACTORIES)
-    static Set<NotifRemoteViewsFactory> provideNotifRemoteViewsFactories(
-            FeatureFlags featureFlags,
-            PrecomputedTextViewFactory precomputedTextViewFactory,
-            BigPictureLayoutInflaterFactory bigPictureLayoutInflaterFactory,
-            CallLayoutSetDataAsyncFactory callLayoutSetDataAsyncFactory
-    ) {
-        final Set<NotifRemoteViewsFactory> replacementFactories = new HashSet<>();
-        replacementFactories.add(precomputedTextViewFactory);
-        if (featureFlags.isEnabled(Flags.BIGPICTURE_NOTIFICATION_LAZY_LOADING)) {
-            replacementFactories.add(bigPictureLayoutInflaterFactory);
-        }
-        if (featureFlags.isEnabled(Flags.CALL_LAYOUT_ASYNC_SET_DATA)) {
-            replacementFactories.add(callLayoutSetDataAsyncFactory);
-        }
-        return replacementFactories;
-    }
+    /**
+     * Provides notification remote view factory container
+     */
+    @Binds
+    @SysUISingleton
+    public abstract NotifRemoteViewsFactoryContainer provideNotifRemoteViewsFactoryContainer(
+            NotifRemoteViewsFactoryContainerImpl containerImpl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
index a52f638..1494c27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
@@ -102,9 +102,9 @@
      * @see InflationFlag
      */
     public void markContentViewsFreeable(@InflationFlag int contentViews) {
-        @InflationFlag int existingContentViews = contentViews &= mContentViews;
+        @InflationFlag int existingFreeableContentViews = contentViews &= mContentViews;
         mContentViews &= ~contentViews;
-        mDirtyContentViews |= existingContentViews;
+        mDirtyContentViews |= existingFreeableContentViews;
     }
 
     public @InflationFlag int getContentViews() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
index b70da00..f4f8374 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
@@ -63,7 +63,10 @@
         @InflationFlag int inflationFlags = params.getContentViews();
         @InflationFlag int invalidatedFlags = params.getDirtyContentViews();
 
+        // Rebind the content views which are needed now, and the corresponding old views are
+        // invalidated
         @InflationFlag int contentToBind = invalidatedFlags & inflationFlags;
+        // Unbind the content views that are not needed
         @InflationFlag int contentToUnbind = inflationFlags ^ FLAG_CONTENT_VIEW_ALL;
 
         // Bind/unbind with parameters
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt
new file mode 100644
index 0000000..d6118a0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt
@@ -0,0 +1,390 @@
+/*
+ * 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.statusbar.notification.row
+
+import android.app.Notification
+import android.app.Notification.MessagingStyle
+import android.app.Person
+import android.content.Context
+import android.graphics.drawable.Icon
+import android.util.Log
+import android.view.LayoutInflater
+import com.android.app.tracing.traceSection
+import com.android.internal.R
+import com.android.internal.widget.MessagingMessage
+import com.android.internal.widget.PeopleHelper
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_SINGLE_LINE
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.ConversationAvatar
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.ConversationData
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.FacePile
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleIcon
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleLineViewModel
+
+/** The inflater of SingleLineViewModel and SingleLineViewHolder */
+internal object SingleLineViewInflater {
+    const val TAG = "SingleLineViewInflater"
+
+    /**
+     * Inflate an instance of SingleLineViewModel.
+     *
+     * @param notification the notification to show
+     * @param messagingStyle the MessagingStyle information is only provided for conversation
+     *   notification, not for legacy messaging notifications
+     * @param builder the recovered Notification Builder
+     * @param systemUiContext the context of Android System UI
+     * @return the inflated SingleLineViewModel
+     */
+    @JvmStatic
+    fun inflateSingleLineViewModel(
+        notification: Notification,
+        messagingStyle: MessagingStyle?,
+        builder: Notification.Builder,
+        systemUiContext: Context,
+    ): SingleLineViewModel {
+        if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) {
+            return SingleLineViewModel(null, null, null)
+        }
+        peopleHelper.init(systemUiContext)
+        var titleText = HybridGroupManager.resolveTitle(notification)
+        var contentText = HybridGroupManager.resolveText(notification)
+
+        if (messagingStyle == null) {
+            return SingleLineViewModel(
+                titleText = titleText,
+                contentText = contentText,
+                conversationData = null,
+            )
+        }
+
+        val isGroupConversation = messagingStyle.isGroupConversation
+
+        val conversationTextData = messagingStyle.loadConversationTextData(systemUiContext)
+        if (conversationTextData?.conversationTitle?.isNotEmpty() == true) {
+            titleText = conversationTextData.conversationTitle
+        }
+        if (conversationTextData?.conversationText?.isNotEmpty() == true) {
+            contentText = conversationTextData.conversationText
+        }
+
+        val conversationAvatar =
+            messagingStyle.loadConversationAvatar(
+                notification = notification,
+                isGroupConversation = isGroupConversation,
+                builder = builder,
+                systemUiContext = systemUiContext
+            )
+
+        val conversationData =
+            ConversationData(
+                // We don't show the sender's name for one-to-one conversation
+                conversationSenderName =
+                    if (isGroupConversation) conversationTextData?.senderName else null,
+                avatar = conversationAvatar
+            )
+
+        return SingleLineViewModel(
+            titleText = titleText,
+            contentText = contentText,
+            conversationData = conversationData,
+        )
+    }
+
+    /** load conversation text data from the MessagingStyle of conversation notifications */
+    private fun MessagingStyle.loadConversationTextData(
+        systemUiContext: Context
+    ): ConversationTextData? {
+        if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) {
+            return null
+        }
+        var conversationText: CharSequence?
+
+        if (messages.isEmpty()) {
+            return null
+        }
+
+        // load the conversation text
+        val lastMessage = messages[messages.lastIndex]
+        conversationText = lastMessage.text
+        if (conversationText == null && lastMessage.isImageMessage()) {
+            conversationText = findBackUpConversationText(lastMessage, systemUiContext)
+        }
+
+        // load the sender's name to display
+        val name = lastMessage.senderPerson?.name
+        val senderName =
+            systemUiContext.resources.getString(
+                R.string.conversation_single_line_name_display,
+                name
+            )
+
+        // We need to find back-up values for those texts if they are needed and empty
+        return ConversationTextData(
+            conversationTitle = conversationTitle
+                    ?: findBackUpConversationTitle(senderName, systemUiContext),
+            conversationText = conversationText,
+            senderName = senderName,
+        )
+    }
+
+    private fun MessagingStyle.Message.isImageMessage(): Boolean = MessagingMessage.hasImage(this)
+
+    /** find a back-up conversation title when the conversation title is null. */
+    private fun MessagingStyle.findBackUpConversationTitle(
+        senderName: CharSequence?,
+        systemUiContext: Context,
+    ): CharSequence {
+        if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) {
+            return ""
+        }
+        return if (isGroupConversation) {
+            systemUiContext.resources.getString(R.string.conversation_title_fallback_group_chat)
+        } else {
+            // Is one-to-one, let's try to use the last sender's name
+            // The last back-up is the value of resource: conversation_title_fallback_one_to_one
+            senderName
+                ?: systemUiContext.resources.getString(
+                    R.string.conversation_title_fallback_one_to_one
+                )
+        }
+    }
+
+    /**
+     * find a back-up conversation text when the conversation has null text and is image message.
+     */
+    private fun findBackUpConversationText(
+        message: MessagingStyle.Message,
+        context: Context,
+    ): CharSequence? {
+        if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) {
+            return null
+        }
+        // If the message is not an image message, just return empty, the back-up text for showing
+        // will be SingleLineViewModel.contentText
+        if (!message.isImageMessage()) return null
+        // If is image message, return a placeholder
+        return context.resources.getString(R.string.conversation_single_line_image_placeholder)
+    }
+
+    /**
+     * The text data that we load from a conversation notification to show in the single-line views.
+     *
+     * Group conversation single-line view should be formatted as:
+     * [conversationTitle, senderName, conversationText]
+     *
+     * One-to-one single-line view should be formatted as:
+     * [conversationTitle (which is equal to the senderName), conversationText]
+     *
+     * @property conversationTitle the title of the conversation, not necessarily the title of the
+     *   notification row. conversationTitle is non-null, though may be empty, in which case we need
+     *   to show the notification title instead.
+     * @property conversationText the text content of the conversation, single-line will use the
+     *   notification's text when conversationText is null
+     * @property senderName the sender's name to be shown in the row when needed. senderName can be
+     *   null
+     */
+    data class ConversationTextData(
+        val conversationTitle: CharSequence,
+        val conversationText: CharSequence?,
+        val senderName: CharSequence?,
+    )
+
+    private fun groupMessages(
+        messages: List<MessagingStyle.Message>,
+        historicMessages: List<MessagingStyle.Message>,
+    ): List<MutableList<MessagingStyle.Message>> {
+        if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) {
+            return listOf()
+        }
+        if (messages.isEmpty() && historicMessages.isEmpty()) return listOf()
+        var currentGroup: MutableList<MessagingStyle.Message>? = null
+        var currentSenderKey: CharSequence? = null
+        val groups = mutableListOf<MutableList<MessagingStyle.Message>>()
+        for (i in 0 until (historicMessages.size + messages.size)) {
+            val message = if (i < historicMessages.size) historicMessages[i] else messages[i]
+
+            val sender = message.senderPerson
+            val senderKey = sender?.getKeyOrName()
+            val isNewGroup = (currentGroup == null) || senderKey != currentSenderKey
+            if (isNewGroup) {
+                currentGroup = mutableListOf()
+                groups.add(currentGroup)
+                currentSenderKey = senderKey
+            }
+            currentGroup?.add(message)
+        }
+        return groups
+    }
+
+    private fun MessagingStyle.loadConversationAvatar(
+        builder: Notification.Builder,
+        notification: Notification,
+        isGroupConversation: Boolean,
+        systemUiContext: Context,
+    ): ConversationAvatar {
+        if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) {
+            return SingleIcon(null)
+        }
+        val userKey = user.getKeyOrName()
+        var conversationIcon: Icon? = null
+        var conversationText: CharSequence? = conversationTitle
+
+        val groups = groupMessages(messages, historicMessages)
+        val uniqueNames = peopleHelper.mapUniqueNamesToPrefixWithGroupList(groups)
+
+        if (!isGroupConversation) {
+            // Conversation is one-to-one, load the single icon
+            // Let's resolve the icon / text from the last sender
+            if (shortcutIcon != null) {
+                conversationIcon = shortcutIcon
+            }
+
+            for (i in messages.lastIndex downTo 0) {
+                val message = messages[i]
+                val sender = message.senderPerson
+                val senderKey = sender?.getKeyOrName()
+                if ((sender != null && senderKey != userKey) || i == 0) {
+                    if (conversationText.isNullOrEmpty()) {
+                        // We use the senderName as header text if no conversation title is provided
+                        // (This usually happens for most 1:1 conversations)
+                        conversationText = sender?.name ?: ""
+                    }
+                    if (conversationIcon == null) {
+                        var avatarIcon = sender?.icon
+                        if (avatarIcon == null) {
+                            avatarIcon = builder.getDefaultAvatar(name = conversationText)
+                        }
+                        conversationIcon = avatarIcon
+                    }
+                    break
+                }
+            }
+        }
+
+        if (conversationIcon == null) {
+            conversationIcon = notification.getLargeIcon()
+        }
+
+        // If is one-to-one or the conversation has an icon, return a single icon
+        if (!isGroupConversation || conversationIcon != null) {
+            return SingleIcon(conversationIcon?.loadDrawable(systemUiContext))
+        }
+
+        // Otherwise, let's find the two last conversations to build a face pile:
+        var secondLastIcon: Icon? = null
+        var lastIcon: Icon? = null
+        var lastKey: CharSequence? = null
+
+        for (i in groups.lastIndex downTo 0) {
+            val message = groups[i][0]
+            val sender = message.senderPerson ?: user
+            val senderKey = sender.getKeyOrName()
+            val notUser = senderKey != userKey
+            val notIncluded = senderKey != lastKey
+
+            if ((notUser && notIncluded) || (i == 0 && lastKey == null)) {
+                if (lastIcon == null) {
+                    lastIcon =
+                        sender.icon
+                            ?: builder.getDefaultAvatar(
+                                name = sender.name,
+                                uniqueNames = uniqueNames
+                            )
+                    lastKey = senderKey
+                } else {
+                    secondLastIcon =
+                        sender.icon
+                            ?: builder.getDefaultAvatar(
+                                name = sender.name,
+                                uniqueNames = uniqueNames
+                            )
+                    break
+                }
+            }
+        }
+
+        if (lastIcon == null) {
+            lastIcon = builder.getDefaultAvatar(name = "")
+        }
+
+        if (secondLastIcon == null) {
+            secondLastIcon = builder.getDefaultAvatar(name = "")
+        }
+
+        return FacePile(
+            topIconDrawable = secondLastIcon.loadDrawable(systemUiContext),
+            bottomIconDrawable = lastIcon.loadDrawable(systemUiContext),
+            bottomBackgroundColor = builder.getBackgroundColor(/* isHeader = */ false),
+        )
+    }
+
+    @JvmStatic
+    fun inflateSingleLineViewHolder(
+        isConversation: Boolean,
+        reinflateFlags: Int,
+        entry: NotificationEntry,
+        context: Context,
+        logger: NotificationContentInflaterLogger,
+    ): HybridNotificationView? {
+        if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return null
+        if (reinflateFlags and FLAG_CONTENT_VIEW_SINGLE_LINE == 0) {
+            return null
+        }
+
+        logger.logInflateSingleLine(entry, reinflateFlags, isConversation)
+        logger.logAsyncTaskProgress(entry, "inflating single-line content view")
+
+        var view: HybridNotificationView? = null
+
+        traceSection("NotificationContentInflater#inflateSingleLineView") {
+            val inflater = LayoutInflater.from(context)
+            val layoutRes: Int =
+                if (isConversation)
+                    com.android.systemui.res.R.layout.hybrid_conversation_notification
+                else com.android.systemui.res.R.layout.hybrid_notification
+            view = inflater.inflate(layoutRes, /* root = */ null) as HybridNotificationView
+            if (view == null) {
+                Log.wtf(TAG, "Single-line view inflation result is null for entry: ${entry.logKey}")
+            }
+        }
+        return view
+    }
+
+    private fun Notification.Builder.getDefaultAvatar(
+        name: CharSequence?,
+        uniqueNames: PeopleHelper.NameToPrefixMap? = null
+    ): Icon {
+        val layoutColor = getSmallIconColor(/* isHeader = */ false)
+        if (!name.isNullOrEmpty()) {
+            val symbol = uniqueNames?.getPrefix(name) ?: ""
+            return peopleHelper.createAvatarSymbol(
+                /* name = */ name,
+                /* symbol = */ symbol,
+                /* layoutColor = */ layoutColor
+            )
+        }
+        // If name is null, create default avatar with background color
+        // TODO(b/319829062): Investigate caching default icon for color
+        return peopleHelper.createAvatarSymbol(/* name = */ "", /* symbol = */ "", layoutColor)
+    }
+
+    private fun Person.getKeyOrName(): CharSequence? = if (key == null) name else key
+
+    private val peopleHelper = PeopleHelper()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineConversationViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineConversationViewBinder.kt
new file mode 100644
index 0000000..69284bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineConversationViewBinder.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.statusbar.notification.row.ui.viewbinder
+
+import com.android.systemui.statusbar.notification.row.HybridConversationNotificationView
+import com.android.systemui.statusbar.notification.row.HybridNotificationView
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleLineViewModel
+
+object SingleLineConversationViewBinder {
+    @JvmStatic
+    fun bind(viewModel: SingleLineViewModel, view: HybridNotificationView?) {
+        if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return
+        if (view !is HybridConversationNotificationView || !viewModel.isConversation()) {
+            SingleLineViewBinder.bind(viewModel, view)
+            return
+        }
+
+        viewModel.conversationData?.avatar?.let { view.setAvatar(it) }
+        view.setText(
+            viewModel.titleText,
+            viewModel.contentText,
+            viewModel.conversationData?.conversationSenderName
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt
new file mode 100644
index 0000000..22e10c1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.statusbar.notification.row.ui.viewbinder
+
+import com.android.systemui.statusbar.notification.row.HybridNotificationView
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleLineViewModel
+
+object SingleLineViewBinder {
+    @JvmStatic
+    fun bind(viewModel: SingleLineViewModel?, view: HybridNotificationView?) {
+        // bind the title and content text views
+        view?.apply {
+            bind(
+                /* title = */ viewModel?.titleText,
+                /* text = */ viewModel?.contentText,
+                /* contentView = */ null
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/SingleLineViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/SingleLineViewModel.kt
new file mode 100644
index 0000000..d583fa5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/SingleLineViewModel.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.statusbar.notification.row.ui.viewmodel
+
+import android.annotation.ColorInt
+import android.graphics.drawable.Drawable
+
+/**
+ * ViewModel for SingleLine Notification View.
+ *
+ * @property titleText the text of notification view title
+ * @property contentText the text of view content
+ * @property conversationData the data that is needed specifically for conversation single-line
+ *   views. Null conversationData shows that the notification is not conversation. Legacy
+ *   MessagingStyle Notifications doesn't have this member.
+ */
+data class SingleLineViewModel(
+    var titleText: CharSequence?,
+    var contentText: CharSequence?,
+    var conversationData: ConversationData?,
+) {
+    fun isConversation(): Boolean {
+        return conversationData != null
+    }
+}
+
+/**
+ * @property conversationSenderName the name of sender to show in the single-line view. Only group
+ *   conversation single-line views show the sender name.
+ * @property avatar the avatar to show for the conversation
+ */
+data class ConversationData(
+    val conversationSenderName: CharSequence?,
+    val avatar: ConversationAvatar,
+)
+
+/**
+ * An avatar to show for a single-line conversation notification, it can be either a single icon or
+ * a face pile.
+ */
+sealed class ConversationAvatar
+
+data class SingleIcon(val iconDrawable: Drawable?) : ConversationAvatar()
+
+/**
+ * A kind of avatar to show for a group conversation notification view. It consists of two avatars
+ * of the last two senders.
+ */
+data class FacePile(
+    val topIconDrawable: Drawable?,
+    val bottomIconDrawable: Drawable?,
+    @ColorInt val bottomBackgroundColor: Int
+) : ConversationAvatar()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 45b9c26..abf6c27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -1295,8 +1295,8 @@
             if (singleLineView != null) {
                 minExpandHeight += singleLineView.getHeight();
             } else {
-                Log.e(TAG, "getMinHeight: child " + child + " single line view is null",
-                        new Exception());
+                Log.e(TAG, "getMinHeight: child " + child.getEntry().getKey()
+                                + " single line view is null", new Exception());
             }
             visibleChildren++;
         }
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 04db653..dd04531 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
@@ -4917,30 +4917,12 @@
     public void removeContainerView(View v) {
         Assert.isMainThread();
         removeView(v);
-        if (!FooterViewRefactor.isEnabled()) {
-            // A notification was removed, and we're not currently showing the empty shade view.
-            if (v instanceof ExpandableNotificationRow && !mController.isShowingEmptyShadeView()) {
-                mController.updateShowEmptyShadeView();
-                updateFooter();
-                mController.updateImportantForAccessibility();
-            }
-        }
-
         updateSpeedBumpIndex();
     }
 
     public void addContainerView(View v) {
         Assert.isMainThread();
         addView(v);
-        if (!FooterViewRefactor.isEnabled()) {
-            // A notification was added, and we're currently showing the empty shade view.
-            if (v instanceof ExpandableNotificationRow && mController.isShowingEmptyShadeView()) {
-                mController.updateShowEmptyShadeView();
-                updateFooter();
-                mController.updateImportantForAccessibility();
-            }
-        }
-
         updateSpeedBumpIndex();
     }
 
@@ -4948,14 +4930,6 @@
         Assert.isMainThread();
         ensureRemovedFromTransientContainer(v);
         addView(v, index);
-        // A notification was added, and we're currently showing the empty shade view.
-        if (!FooterViewRefactor.isEnabled() && v instanceof ExpandableNotificationRow
-                && mController.isShowingEmptyShadeView()) {
-            mController.updateShowEmptyShadeView();
-            updateFooter();
-            mController.updateImportantForAccessibility();
-        }
-
         updateSpeedBumpIndex();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 1143481..49fde39 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -2137,6 +2137,7 @@
 
             if (!FooterViewRefactor.isEnabled()) {
                 updateShowEmptyShadeView();
+                updateImportantForAccessibility();
             }
         }
     }
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 f842e30..fe5bdd4 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
@@ -18,9 +18,12 @@
 
 import android.animation.Animator
 import android.animation.AnimatorListenerAdapter
+import android.view.View
+import android.view.WindowInsets
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
@@ -30,6 +33,9 @@
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.update
 import kotlinx.coroutines.launch
 
 /** Binds the shared notification container to its view-model. */
@@ -65,6 +71,8 @@
                 }
             }
 
+        val burnInParams = MutableStateFlow(BurnInParameters())
+
         /*
          * For animation sensitive coroutines, immediately run just like applicationScope does
          * instead of doing a post() to the main thread. This extra delay can cause visible jitter.
@@ -122,7 +130,11 @@
                         }
                     }
 
-                    launch { viewModel.translationY.collect { controller.setTranslationY(it) } }
+                    launch {
+                        burnInParams
+                            .flatMapLatest { params -> viewModel.translationY(params) }
+                            .collect { y -> controller.setTranslationY(y) }
+                    }
 
                     launch {
                         viewModel.expansionAlpha.collect { controller.setMaxAlphaForExpansion(it) }
@@ -137,11 +149,20 @@
 
         controller.setOnHeightChangedRunnable(Runnable { viewModel.notificationStackChanged() })
 
+        view.setOnApplyWindowInsetsListener { v: View, insets: WindowInsets ->
+            val insetTypes = WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout()
+            burnInParams.update { current ->
+                current.copy(topInset = insets.getInsetsIgnoringVisibility(insetTypes).top)
+            }
+            insets
+        }
+
         return object : DisposableHandle {
             override fun dispose() {
                 disposableHandle.dispose()
                 disposableHandleMainImmediate.dispose()
                 controller.setOnHeightChangedRunnable(null)
+                view.setOnApplyWindowInsetsListener(null)
             }
         }
     }
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 99cd89b..4617ce4 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
@@ -28,6 +28,8 @@
 import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
 import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
 import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
+import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
+import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
 import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGlanceableHubTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel
@@ -65,10 +67,11 @@
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val shadeInteractor: ShadeInteractor,
     communalInteractor: CommunalInteractor,
-    occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
+    private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
     lockscreenToOccludedTransitionViewModel: LockscreenToOccludedTransitionViewModel,
     glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel,
-    lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel
+    lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel,
+    private val aodBurnInViewModel: AodBurnInViewModel,
 ) {
     private val statesForConstrainedNotifications =
         setOf(
@@ -313,20 +316,22 @@
      * Under certain scenarios, such as swiping up on the lockscreen, the container will need to be
      * translated as the keyguard fades out.
      */
-    val translationY: Flow<Float> =
-        combine(
+    fun translationY(params: BurnInParameters): Flow<Float> {
+        return combine(
+            aodBurnInViewModel.translationY(params).onStart { emit(0f) },
             isOnLockscreenWithoutShade,
             merge(
                 keyguardInteractor.keyguardTranslationY,
                 occludedToLockscreenTransitionViewModel.lockscreenTranslationY,
             )
-        ) { isOnLockscreenWithoutShade, translationY ->
+        ) { burnInY, isOnLockscreenWithoutShade, translationY ->
             if (isOnLockscreenWithoutShade) {
-                translationY
+                burnInY + translationY
             } else {
                 0f
             }
         }
+    }
 
     /**
      * When on keyguard, there is limited space to display notifications so calculate how many could
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 266c19c..7952511 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -2587,8 +2587,7 @@
                 // So if AOD is off or unsupported we need to trigger these updates at screen on
                 // when the keyguard is occluded.
                 mLockscreenUserManager.updatePublicMode();
-                mShadeSurface.getNotificationStackScrollLayoutController()
-                        .updateSensitivenessForOccludedWakeup();
+                mStackScrollerController.updateSensitivenessForOccludedWakeup();
             }
             if (mLaunchCameraWhenFinishedWaking) {
                 mCameraLauncherLazy.get().launchCamera(mLastCameraLaunchSource,
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 88347ab..4c83ca2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -69,6 +69,7 @@
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.KeyguardWmStateRefactor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
@@ -474,7 +475,7 @@
             mIsDocked = mDockManager.isDocked();
         }
 
-        if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+        if (KeyguardWmStateRefactor.isEnabled()) {
             // Show the keyguard views whenever we've told WM that the lockscreen is visible.
             mShadeViewController.postToView(() ->
                     collectFlow(
@@ -1428,7 +1429,7 @@
             executeAfterKeyguardGoneAction();
         }
 
-        if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+        if (KeyguardWmStateRefactor.isEnabled()) {
             mKeyguardTransitionInteractor.startDismissKeyguardTransition();
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index 20d1fff..a2d8d15 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -28,6 +28,8 @@
 import android.icu.text.DateTimePatternGenerator;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
 import android.os.Parcelable;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -48,11 +50,11 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.Dependency;
 import com.android.systemui.FontSizeUtils;
-import com.android.systemui.res.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.demomode.DemoModeCommandReceiver;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -106,6 +108,7 @@
     private final int mAmPmStyle;
     private boolean mShowSeconds;
     private Handler mSecondsHandler;
+    private HandlerThread mHandlerThread;
 
     // Fields to cache the width so the clock remains at an approximately constant width
     private int mCharsAtCurrentWidth = -1;
@@ -146,6 +149,8 @@
         }
         mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class);
         mUserTracker = Dependency.get(UserTracker.class);
+        mHandlerThread = new HandlerThread("Clock");
+        mHandlerThread.start();
 
         setIncludeFontPadding(false);
     }
@@ -205,7 +210,8 @@
             Dependency.get(TunerService.class).addTunable(this, CLOCK_SECONDS,
                     StatusBarIconController.ICON_HIDE_LIST);
             mCommandQueue.addCallback(this);
-            mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
+            mUserTracker.addCallback(mUserChangedCallback,
+                       new HandlerExecutor(mHandlerThread.getThreadHandler()));
             mCurrentUserId = mUserTracker.getUserId();
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
index b7d8ee3..a7440d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
@@ -21,6 +21,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
 import android.os.UserHandle;
 
 import androidx.annotation.NonNull;
@@ -51,6 +53,7 @@
     private final UserTracker mUserTracker;
     private AlarmManager mAlarmManager;
     private AlarmManager.AlarmClockInfo mNextAlarm;
+    private HandlerThread mHandlerThread;
 
     private final UserTracker.Callback mUserChangedCallback =
             new UserTracker.Callback() {
@@ -75,7 +78,10 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
         broadcastDispatcher.registerReceiver(this, filter, null, UserHandle.ALL);
-        mUserTracker.addCallback(mUserChangedCallback, mainExecutor);
+        mHandlerThread = new HandlerThread("NextAlarmControllerImpl");
+        mHandlerThread.start();
+        mUserTracker.addCallback(mUserChangedCallback,
+                    new HandlerExecutor(mHandlerThread.getThreadHandler()));
         updateNextAlarm();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 9f4a906..6a6efbc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -157,7 +157,7 @@
         // TODO: re-register network callback on user change.
         mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback);
         onUserSwitched(mUserTracker.getUserId());
-        mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
+        mUserTracker.addCallback(mUserChangedCallback, mBgExecutor);
     }
 
     public void dump(PrintWriter pw, String[] args) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
index 2ed9d15..0bc0e88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
@@ -36,9 +36,9 @@
 
 import com.android.internal.util.UserIcons;
 import com.android.settingslib.drawable.UserIconDrawable;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 
 import java.util.ArrayList;
@@ -66,11 +66,11 @@
     /**
      */
     @Inject
-    public UserInfoControllerImpl(Context context, @Main Executor mainExecutor,
+    public UserInfoControllerImpl(Context context, @Background Executor bgExecutor,
             UserTracker userTracker) {
         mContext = context;
         mUserTracker = userTracker;
-        mUserTracker.addCallback(mUserChangedCallback, mainExecutor);
+        mUserTracker.addCallback(mUserChangedCallback, bgExecutor);
 
         IntentFilter profileFilter = new IntentFilter();
         profileFilter.addAction(ContactsContract.Intents.ACTION_PROFILE_CHANGED);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index df210b0..f0b4930 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -29,6 +29,7 @@
 import android.net.Uri;
 import android.os.Handler;
 import android.os.HandlerExecutor;
+import android.os.HandlerThread;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -81,6 +82,7 @@
     private volatile int mZenMode;
     private long mZenUpdateTime;
     private NotificationManager.Policy mConsolidatedNotificationPolicy;
+    private HandlerThread mHandlerThread;
 
     private final UserTracker.Callback mUserChangedCallback =
             new UserTracker.Callback() {
@@ -133,6 +135,8 @@
                 }
             }
         };
+        mHandlerThread = new HandlerThread("ZenModeControllerImpl");
+        mHandlerThread.start();
         mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
         globalSettings.registerContentObserver(Global.ZEN_MODE, modeContentObserver);
         updateZenMode(getModeSettingValueFromProvider());
@@ -143,7 +147,8 @@
         mSetupObserver = new SetupObserver(handler);
         mSetupObserver.register();
         mUserManager = context.getSystemService(UserManager.class);
-        mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(handler));
+        mUserTracker.addCallback(mUserChangedCallback,
+                new HandlerExecutor(mHandlerThread.getThreadHandler()));
         // This registers the alarm broadcast receiver for the current user
         mUserChangedCallback.onUserChanged(getCurrentUser(), context);
 
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 2b9ad50..77518db 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -480,7 +480,7 @@
             return;
         }
 
-        mUserTracker.addCallback(mUserTrackerCallback, mMainExecutor);
+        mUserTracker.addCallback(mUserTrackerCallback, mBgExecutor);
 
         mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
 
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index 550a65c..f5b4d17 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -26,6 +26,7 @@
 import android.net.Uri;
 import android.os.Handler;
 import android.os.HandlerExecutor;
+import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -38,11 +39,11 @@
 
 import com.android.internal.util.ArrayUtils;
 import com.android.systemui.DejankUtils;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -98,6 +99,7 @@
     private UserTracker.Callback mCurrentUserTracker;
     private UserTracker mUserTracker;
     private final ComponentName mTunerComponent;
+    private HandlerThread mHandlerThread;
 
     /**
      */
@@ -117,7 +119,8 @@
         mDemoModeController = demoModeController;
         mUserTracker = userTracker;
         mTunerComponent = new ComponentName(mContext, TunerActivity.class);
-
+        mHandlerThread = new HandlerThread("TunerServiceImpl");
+        mHandlerThread.start();
         for (UserInfo user : UserManager.get(mContext).getUsers()) {
             mCurrentUser = user.getUserHandle().getIdentifier();
             if (getValue(TUNER_VERSION, 0) != CURRENT_TUNER_VERSION) {
@@ -135,7 +138,7 @@
             }
         };
         mUserTracker.addCallback(mCurrentUserTracker,
-                new HandlerExecutor(mainHandler));
+                new HandlerExecutor(mHandlerThread.getThreadHandler()));
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index cf76c0d..74e1339 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -190,7 +190,7 @@
                         }
                     }
 
-                tracker.addCallback(callback, mainDispatcher.asExecutor())
+                tracker.addCallback(callback, backgroundDispatcher.asExecutor())
                 send(currentSelectionStatus)
 
                 awaitClose { tracker.removeCallback(callback) }
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/BooleanFlowOperators.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/BooleanFlowOperators.kt
new file mode 100644
index 0000000..693a835
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/BooleanFlowOperators.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.util.kotlin
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+
+object BooleanFlowOperators {
+    /**
+     * Logical AND operator for boolean flows. Will collect all flows and [combine] them to
+     * determine the result.
+     *
+     * Usage:
+     * ```
+     * val result = and(flow1, flow2)
+     * ```
+     */
+    fun and(vararg flows: Flow<Boolean>): Flow<Boolean> =
+        combine(flows.asIterable()) { values -> values.all { it } }
+
+    /**
+     * Logical NOT operator for a boolean flow.
+     *
+     * Usage:
+     * ```
+     * val negatedFlow = not(flow)
+     * ```
+     */
+    fun not(flow: Flow<Boolean>) = flow.map { !it }
+
+    /**
+     * Logical OR operator for a boolean flow. Will collect all flows and [combine] them to
+     * determine the result.
+     */
+    fun or(vararg flows: Flow<Boolean>): Flow<Boolean> =
+        combine(flows.asIterable()) { values -> values.any { it } }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt
index c587f2e..5150389 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.util.kotlin
 
 import dagger.Lazy
+import java.util.Optional
 import kotlin.reflect.KProperty
 
 /**
@@ -30,3 +31,16 @@
  * ```
  */
 operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = get()
+
+/**
+ * Extension operator that allows developers to use [java.util.Optional] as a nullable property
+ * delegate:
+ * ```kotlin
+ *    class MyClass @Inject constructor(
+ *      optionalDependency: Optional<Foo>,
+ *    ) {
+ *      val dependency: Foo? by optionalDependency
+ *    }
+ * ```
+ */
+operator fun <T> Optional<T>.getValue(thisRef: Any?, property: KProperty<*>): T? = getOrNull()
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 1e801ae..7c6ad23 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -32,6 +32,8 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.inputmethodservice.InputMethodService;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
 import android.os.IBinder;
 import android.util.Log;
 import android.view.Display;
@@ -125,6 +127,7 @@
     private final DisplayTracker mDisplayTracker;
     private final NoteTaskInitializer mNoteTaskInitializer;
     private final Executor mSysUiMainExecutor;
+    private HandlerThread mHandlerThread;
 
     // Listeners and callbacks. Note that we prefer member variable over anonymous class here to
     // avoid the situation that some implementations, like KeyguardUpdateMonitor, use WeakReference
@@ -206,6 +209,8 @@
         mDisplayTracker = displayTracker;
         mNoteTaskInitializer = noteTaskInitializer;
         mSysUiMainExecutor = sysUiMainExecutor;
+        mHandlerThread = new HandlerThread("WMShell");
+        mHandlerThread.start();
     }
 
     @Override
@@ -219,7 +224,8 @@
         mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
 
         // Subscribe to user changes
-        mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
+        mUserTracker.addCallback(mUserChangedCallback,
+                     new HandlerExecutor(mHandlerThread.getThreadHandler()));
 
         mCommandQueue.addCallback(this);
         mPipOptional.ifPresent(this::initPip);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index d06457b..be06cc5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -121,7 +121,6 @@
 import com.android.keyguard.KeyguardUpdateMonitor.BiometricAuthenticated;
 import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
 import com.android.settingslib.fuelgauge.BatteryStatus;
-import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
@@ -930,7 +929,8 @@
     @Test
     public void trustAgentHasTrust() {
         // WHEN user has trust
-        givenSelectedUserCanSkipBouncerFromTrustedState();
+        mKeyguardUpdateMonitor.onTrustChanged(true, true,
+                mSelectedUserInteractor.getSelectedUserId(), 0, null);
 
         // THEN user is considered as "having trust" and bouncer can be skipped
         Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(
@@ -954,7 +954,8 @@
     @Test
     public void trustAgentHasTrust_fingerprintLockout() {
         // GIVEN user has trust
-        givenSelectedUserCanSkipBouncerFromTrustedState();
+        mKeyguardUpdateMonitor.onTrustChanged(true, true,
+                mSelectedUserInteractor.getSelectedUserId(), 0, null);
         Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(
                 mSelectedUserInteractor.getSelectedUserId()));
 
@@ -2015,43 +2016,6 @@
     }
 
     @Test
-    public void runFpDetectFlagDisabled_sideFps_keyguardDismissible_fingerprintAuthenticateRuns() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_RUN_FINGERPRINT_DETECT_ON_DISMISSIBLE_KEYGUARD);
-
-        // Clear invocations, since previous setup (e.g. registering BiometricManager callbacks)
-        // will trigger updateBiometricListeningState();
-        clearInvocations(mFingerprintManager);
-        mKeyguardUpdateMonitor.resetBiometricListeningState();
-
-        // GIVEN the user can skip the bouncer
-        givenSelectedUserCanSkipBouncerFromTrustedState();
-        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
-        mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
-        mTestableLooper.processAllMessages();
-
-        // WHEN verify authenticate runs
-        verifyFingerprintAuthenticateCall();
-    }
-
-    @Test
-    public void sideFps_keyguardDismissible_fingerprintDetectRuns() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_RUN_FINGERPRINT_DETECT_ON_DISMISSIBLE_KEYGUARD);
-        // Clear invocations, since previous setup (e.g. registering BiometricManager callbacks)
-        // will trigger updateBiometricListeningState();
-        clearInvocations(mFingerprintManager);
-        mKeyguardUpdateMonitor.resetBiometricListeningState();
-
-        // GIVEN the user can skip the bouncer
-        givenSelectedUserCanSkipBouncerFromTrustedState();
-        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
-        mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
-        mTestableLooper.processAllMessages();
-
-        // WHEN verify detect runs
-        verifyFingerprintDetectCall();
-    }
-
-    @Test
     public void testFingerprintSensorProperties() throws RemoteException {
         mFingerprintAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(
                 new ArrayList<>());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
index 9bcab57..9087816 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
@@ -16,10 +16,12 @@
 
 package com.android.systemui.accessibility.floatingmenu;
 
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
+import android.annotation.NonNull;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.WindowManager;
@@ -27,10 +29,12 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.accessibility.utils.TestUtils;
 import com.android.systemui.util.settings.SecureSettings;
-import com.android.wm.shell.bubbles.DismissViewUtils;
 import com.android.wm.shell.common.bubbles.DismissView;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -46,6 +50,7 @@
 @TestableLooper.RunWithLooper
 public class DragToInteractAnimationControllerTest extends SysuiTestCase {
     private DragToInteractAnimationController mDragToInteractAnimationController;
+    private DragToInteractView mInteractView;
     private DismissView mDismissView;
 
     @Rule
@@ -57,29 +62,72 @@
     @Before
     public void setUp() throws Exception {
         final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
+        final SecureSettings mockSecureSettings = TestUtils.mockSecureSettings();
         final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
-                mock(SecureSettings.class));
+                mockSecureSettings);
         final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
                 stubWindowManager);
-        final MenuView stubMenuView = new MenuView(mContext, stubMenuViewModel,
-                stubMenuViewAppearance);
+        final MenuView stubMenuView = spy(new MenuView(mContext, stubMenuViewModel,
+                stubMenuViewAppearance, mockSecureSettings));
+        mInteractView = spy(new DragToInteractView(mContext));
         mDismissView = spy(new DismissView(mContext));
-        DismissViewUtils.setup(mDismissView);
-        mDragToInteractAnimationController = new DragToInteractAnimationController(
-                mDismissView, stubMenuView);
+
+        if (Flags.floatingMenuDragToEdit()) {
+            mDragToInteractAnimationController = new DragToInteractAnimationController(
+                    mInteractView, stubMenuView);
+        } else {
+            mDragToInteractAnimationController = new DragToInteractAnimationController(
+                    mDismissView, stubMenuView);
+        }
+
+        mDragToInteractAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
+            @Override
+            public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+
+            }
+
+            @Override
+            public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
+                    float velX, float velY, boolean wasFlungOut) {
+
+            }
+
+            @Override
+            public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+
+            }
+        });
     }
 
     @Test
-    public void showDismissView_success() {
-        mDragToInteractAnimationController.showDismissView(true);
+    @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+    public void showDismissView_success_old() {
+        mDragToInteractAnimationController.showInteractView(true);
 
         verify(mDismissView).show();
     }
 
     @Test
-    public void hideDismissView_success() {
-        mDragToInteractAnimationController.showDismissView(false);
+    @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+    public void hideDismissView_success_old() {
+        mDragToInteractAnimationController.showInteractView(false);
 
         verify(mDismissView).hide();
     }
+
+    @Test
+    @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+    public void showDismissView_success() {
+        mDragToInteractAnimationController.showInteractView(true);
+
+        verify(mInteractView).show();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+    public void hideDismissView_success() {
+        mDragToInteractAnimationController.showInteractView(false);
+
+        verify(mInteractView).hide();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
index 215f93d..e0df1e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
@@ -17,6 +17,7 @@
 package com.android.systemui.accessibility.floatingmenu;
 
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -42,6 +43,7 @@
 import com.android.systemui.Flags;
 import com.android.systemui.Prefs;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.accessibility.utils.TestUtils;
 import com.android.systemui.util.settings.SecureSettings;
 
 import org.junit.After;
@@ -79,10 +81,12 @@
         final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
         final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
                 stubWindowManager);
+        final SecureSettings secureSettings = TestUtils.mockSecureSettings();
         final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
-                mock(SecureSettings.class));
+                secureSettings);
 
-        mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance));
+        mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance,
+                secureSettings));
         mViewPropertyAnimator = spy(mMenuView.animate());
         doReturn(mViewPropertyAnimator).when(mMenuView).animate();
 
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 9c8de30..c2ed7d4 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
@@ -22,10 +22,13 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
 import android.graphics.Rect;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.WindowManager;
@@ -37,7 +40,9 @@
 import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.accessibility.utils.TestUtils;
 import com.android.systemui.res.R;
 import com.android.systemui.util.settings.SecureSettings;
 
@@ -49,6 +54,8 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import java.util.concurrent.atomic.AtomicBoolean;
+
 /** Tests for {@link MenuItemAccessibilityDelegate}. */
 @SmallTest
 @TestableLooper.RunWithLooper
@@ -59,17 +66,16 @@
 
     @Mock
     private AccessibilityManager mAccessibilityManager;
-    @Mock
-    private SecureSettings mSecureSettings;
-    @Mock
-    private DragToInteractAnimationController.DismissCallback mStubDismissCallback;
-
+    private final SecureSettings mSecureSettings = TestUtils.mockSecureSettings();
     private RecyclerView mStubListView;
     private MenuView mMenuView;
+    private MenuViewLayer mMenuViewLayer;
     private MenuItemAccessibilityDelegate mMenuItemAccessibilityDelegate;
     private MenuAnimationController mMenuAnimationController;
     private final Rect mDraggableBounds = new Rect(100, 200, 300, 400);
 
+    private final AtomicBoolean mEditReceived = new AtomicBoolean(false);
+
     @Before
     public void setUp() {
         final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
@@ -80,20 +86,28 @@
 
         final int halfScreenHeight =
                 stubWindowManager.getCurrentWindowMetrics().getBounds().height() / 2;
-        mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance));
+        mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance,
+                mSecureSettings));
         mMenuView.setTranslationY(halfScreenHeight);
 
+        mMenuViewLayer = spy(new MenuViewLayer(
+                mContext, stubWindowManager, mAccessibilityManager,
+                stubMenuViewModel, stubMenuViewAppearance, mMenuView,
+                mock(IAccessibilityFloatingMenu.class), mSecureSettings));
+
         doReturn(mDraggableBounds).when(mMenuView).getMenuDraggableBounds();
         mStubListView = new RecyclerView(mContext);
         mMenuAnimationController = spy(new MenuAnimationController(mMenuView,
                 stubMenuViewAppearance));
         mMenuItemAccessibilityDelegate =
                 new MenuItemAccessibilityDelegate(new RecyclerViewAccessibilityDelegate(
-                        mStubListView), mMenuAnimationController);
+                        mStubListView), mMenuAnimationController, mMenuViewLayer);
+        mEditReceived.set(false);
     }
 
     @Test
-    public void getAccessibilityActionList_matchSize() {
+    @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+    public void getAccessibilityActionList_matchSize_withoutEdit() {
         final AccessibilityNodeInfoCompat info =
                 new AccessibilityNodeInfoCompat(new AccessibilityNodeInfo());
 
@@ -103,6 +117,17 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+    public void getAccessibilityActionList_matchSize() {
+        final AccessibilityNodeInfoCompat info =
+                new AccessibilityNodeInfoCompat(new AccessibilityNodeInfo());
+
+        mMenuItemAccessibilityDelegate.onInitializeAccessibilityNodeInfo(mStubListView, info);
+
+        assertThat(info.getActionList().size()).isEqualTo(7);
+    }
+
+    @Test
     public void performMoveTopLeftAction_matchPosition() {
         final boolean moveTopLeftAction =
                 mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView,
@@ -169,13 +194,22 @@
 
     @Test
     public void performRemoveMenuAction_success() {
-        mMenuAnimationController.setDismissCallback(mStubDismissCallback);
         final boolean removeMenuAction =
                 mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView,
                         R.id.action_remove_menu, null);
 
         assertThat(removeMenuAction).isTrue();
-        verify(mMenuAnimationController).removeMenu();
+        verify(mMenuViewLayer).dispatchAccessibilityAction(R.id.action_remove_menu);
+    }
+
+    @Test
+    public void performEditAction_success() {
+        final boolean editAction =
+                mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView,
+                        R.id.action_edit, null);
+
+        assertThat(editAction).isTrue();
+        verify(mMenuViewLayer).dispatchAccessibilityAction(R.id.action_edit);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
index e1522f5..9e8c6b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.accessibility.floatingmenu;
 
+import static android.R.id.empty;
 import static android.view.View.OVER_SCROLL_NEVER;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -27,6 +28,8 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.MotionEvent;
@@ -38,10 +41,11 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.accessibility.dialog.AccessibilityTarget;
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.accessibility.MotionEventHelper;
+import com.android.systemui.accessibility.utils.TestUtils;
 import com.android.systemui.util.settings.SecureSettings;
-import com.android.wm.shell.bubbles.DismissViewUtils;
 import com.android.wm.shell.common.bubbles.DismissView;
 
 import org.junit.After;
@@ -71,6 +75,7 @@
     private DragToInteractAnimationController mDragToInteractAnimationController;
     private RecyclerView mStubListView;
     private DismissView mDismissView;
+    private DragToInteractView mInteractView;
 
     @Rule
     public MockitoRule mockito = MockitoJUnit.rule();
@@ -81,19 +86,28 @@
     @Before
     public void setUp() throws Exception {
         final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
+        final SecureSettings secureSettings = TestUtils.mockSecureSettings();
         final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
-                mock(SecureSettings.class));
+                secureSettings);
         final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
                 windowManager);
-        mStubMenuView = new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance);
+        mStubMenuView = new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance,
+                secureSettings);
         mStubMenuView.setTranslationX(0);
         mStubMenuView.setTranslationY(0);
         mMenuAnimationController = spy(new MenuAnimationController(
                 mStubMenuView, stubMenuViewAppearance));
+        mInteractView = spy(new DragToInteractView(mContext));
         mDismissView = spy(new DismissView(mContext));
-        DismissViewUtils.setup(mDismissView);
-        mDragToInteractAnimationController =
-                spy(new DragToInteractAnimationController(mDismissView, mStubMenuView));
+
+        if (Flags.floatingMenuDragToEdit()) {
+            mDragToInteractAnimationController = spy(new DragToInteractAnimationController(
+                    mInteractView, mStubMenuView));
+        } else {
+            mDragToInteractAnimationController = spy(new DragToInteractAnimationController(
+                    mDismissView, mStubMenuView));
+        }
+
         mTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController,
                 mDragToInteractAnimationController);
         final AccessibilityTargetAdapter stubAdapter = new AccessibilityTargetAdapter(mStubTargets);
@@ -115,7 +129,7 @@
 
     @Test
     public void onActionMoveEvent_notConsumedEvent_shouldMoveToPosition() {
-        doReturn(false).when(mDragToInteractAnimationController).maybeConsumeMoveMotionEvent(
+        doReturn(empty).when(mDragToInteractAnimationController).maybeConsumeMoveMotionEvent(
                 any(MotionEvent.class));
         final int offset = 100;
         final MotionEvent stubDownEvent =
@@ -136,6 +150,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
     public void onActionMoveEvent_shouldShowDismissView() {
         final int offset = 100;
         final MotionEvent stubDownEvent =
@@ -154,6 +169,25 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+    public void onActionMoveEvent_shouldShowInteractView() {
+        final int offset = 100;
+        final MotionEvent stubDownEvent =
+                mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 1,
+                        MotionEvent.ACTION_DOWN, mStubMenuView.getTranslationX(),
+                        mStubMenuView.getTranslationY());
+        final MotionEvent stubMoveEvent =
+                mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 3,
+                        MotionEvent.ACTION_MOVE, mStubMenuView.getTranslationX() + offset,
+                        mStubMenuView.getTranslationY() + offset);
+
+        mTouchHandler.onInterceptTouchEvent(mStubListView, stubDownEvent);
+        mTouchHandler.onInterceptTouchEvent(mStubListView, stubMoveEvent);
+
+        verify(mInteractView).show();
+    }
+
+    @Test
     public void dragAndDrop_shouldFlingMenuThenSpringToEdge() {
         final int offset = 100;
         final MotionEvent stubDownEvent =
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 bc9a0a5..4a1bdbc 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
@@ -30,6 +30,7 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
@@ -72,6 +73,8 @@
 import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.SysuiTestableContext;
+import com.android.systemui.accessibility.utils.TestUtils;
+import com.android.systemui.res.R;
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
 
@@ -81,6 +84,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
 import org.mockito.Spy;
 import org.mockito.junit.MockitoJUnit;
@@ -122,18 +126,17 @@
     private SysuiTestableContext mSpyContext = getContext();
     @Mock
     private IAccessibilityFloatingMenu mFloatingMenu;
-
-    @Mock
-    private SecureSettings mSecureSettings;
-
     @Mock
     private WindowManager mStubWindowManager;
-
     @Mock
     private AccessibilityManager mStubAccessibilityManager;
+    private final SecureSettings mSecureSettings = TestUtils.mockSecureSettings();
 
     private final NotificationManager mMockNotificationManager = mock(NotificationManager.class);
 
+    private final ArgumentMatcher<IntentFilter> mNotificationMatcher =
+            (arg) -> arg.hasAction(ACTION_UNDO) && arg.hasAction(ACTION_DELETE);
+
     @Before
     public void setUp() throws Exception {
         mSpyContext.addMockSystemService(Context.NOTIFICATION_SERVICE, mMockNotificationManager);
@@ -145,8 +148,16 @@
                 new WindowMetrics(mDisplayBounds, fakeDisplayInsets(), /* density = */ 0.0f));
         doReturn(mWindowMetrics).when(mStubWindowManager).getCurrentWindowMetrics();
 
-        mMenuViewLayer = new MenuViewLayer(mSpyContext, mStubWindowManager,
-                mStubAccessibilityManager, mFloatingMenu, mSecureSettings);
+        MenuViewModel menuViewModel = new MenuViewModel(
+                mSpyContext, mStubAccessibilityManager, mSecureSettings);
+        MenuViewAppearance menuViewAppearance = new MenuViewAppearance(
+                mSpyContext, mStubWindowManager);
+        mMenuView = spy(
+                new MenuView(mSpyContext, menuViewModel, menuViewAppearance, mSecureSettings));
+
+        mMenuViewLayer = spy(new MenuViewLayer(mSpyContext, mStubWindowManager,
+                mStubAccessibilityManager, menuViewModel, menuViewAppearance, mMenuView,
+                mFloatingMenu, mSecureSettings));
         mMenuView = (MenuView) mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW);
         mMenuAnimationController = mMenuView.getMenuAnimationController();
 
@@ -236,6 +247,27 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+    public void onEditAction_gotoEditScreen_isCalled() {
+        mMenuViewLayer.dispatchAccessibilityAction(R.id.action_edit);
+        verify(mMenuView).gotoEditScreen();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE)
+    public void onDismissAction_hideMenuAndShowNotification() {
+        mMenuViewLayer.dispatchAccessibilityAction(R.id.action_remove_menu);
+        verify(mMenuViewLayer).hideMenuAndShowNotification();
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE)
+    public void onDismissAction_hideMenuAndShowMessage() {
+        mMenuViewLayer.dispatchAccessibilityAction(R.id.action_remove_menu);
+        verify(mMenuViewLayer).hideMenuAndShowMessage();
+    }
+
+    @Test
     public void showingImeInsetsChange_notOverlapOnIme_menuKeepOriginalPosition() {
         final float menuTop = STATUS_BAR_HEIGHT + 100;
         mMenuAnimationController.moveAndPersistPosition(new PointF(0, menuTop));
@@ -307,19 +339,13 @@
     @Test
     @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE)
     public void onReleasedInTarget_hideMenuAndShowNotificationWithExpectedActions() {
-        dragMenuThenReleasedInTarget();
+        dragMenuThenReleasedInTarget(R.id.action_remove_menu);
 
         verify(mMockNotificationManager).notify(
                 eq(SystemMessageProto.SystemMessage.NOTE_A11Y_FLOATING_MENU_HIDDEN),
                 any(Notification.class));
-        ArgumentCaptor<IntentFilter> intentFilterCaptor = ArgumentCaptor.forClass(
-                IntentFilter.class);
         verify(mSpyContext).registerReceiver(
-                any(BroadcastReceiver.class),
-                intentFilterCaptor.capture(),
-                anyInt());
-        assertThat(intentFilterCaptor.getValue().matchAction(ACTION_UNDO)).isTrue();
-        assertThat(intentFilterCaptor.getValue().matchAction(ACTION_DELETE)).isTrue();
+                any(BroadcastReceiver.class), argThat(mNotificationMatcher), anyInt());
     }
 
     @Test
@@ -327,10 +353,10 @@
     public void receiveActionUndo_dismissNotificationAndMenuVisible() {
         ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass(
                 BroadcastReceiver.class);
-        dragMenuThenReleasedInTarget();
+        dragMenuThenReleasedInTarget(R.id.action_remove_menu);
 
         verify(mSpyContext).registerReceiver(broadcastReceiverCaptor.capture(),
-                any(IntentFilter.class), anyInt());
+                argThat(mNotificationMatcher), anyInt());
         broadcastReceiverCaptor.getValue().onReceive(mSpyContext, new Intent(ACTION_UNDO));
 
         verify(mSpyContext).unregisterReceiver(broadcastReceiverCaptor.getValue());
@@ -344,10 +370,10 @@
     public void receiveActionDelete_dismissNotificationAndHideMenu() {
         ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass(
                 BroadcastReceiver.class);
-        dragMenuThenReleasedInTarget();
+        dragMenuThenReleasedInTarget(R.id.action_remove_menu);
 
         verify(mSpyContext).registerReceiver(broadcastReceiverCaptor.capture(),
-                any(IntentFilter.class), anyInt());
+                argThat(mNotificationMatcher), anyInt());
         broadcastReceiverCaptor.getValue().onReceive(mSpyContext, new Intent(ACTION_DELETE));
 
         verify(mSpyContext).unregisterReceiver(broadcastReceiverCaptor.getValue());
@@ -423,10 +449,12 @@
                 });
     }
 
-    private void dragMenuThenReleasedInTarget() {
+    private void dragMenuThenReleasedInTarget(int id) {
         MagnetizedObject.MagnetListener magnetListener =
-                mMenuViewLayer.getDragToInteractAnimationController().getMagnetListener();
+                mMenuViewLayer.getDragToInteractAnimationController().getMagnetListener(id);
+        View view = mock(View.class);
+        when(view.getId()).thenReturn(id);
         magnetListener.onReleasedInTarget(
-                new MagnetizedObject.MagneticTarget(mock(View.class), 200));
+                new MagnetizedObject.MagneticTarget(view, 200));
     }
 }
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 8da6cf9..7c97f53 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
@@ -17,15 +17,19 @@
 package com.android.systemui.accessibility.floatingmenu;
 
 import static android.app.UiModeManager.MODE_NIGHT_YES;
+
 import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.mock;
+
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
 import android.app.UiModeManager;
+import android.content.Intent;
 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;
@@ -36,6 +40,8 @@
 import com.android.systemui.Flags;
 import com.android.systemui.Prefs;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.SysuiTestableContext;
+import com.android.systemui.accessibility.utils.TestUtils;
 import com.android.systemui.util.settings.SecureSettings;
 
 import org.junit.After;
@@ -65,17 +71,23 @@
     @Mock
     private AccessibilityManager mAccessibilityManager;
 
+    private SysuiTestableContext mSpyContext;
+
     @Before
     public void setUp() throws Exception {
         mUiModeManager = mContext.getSystemService(UiModeManager.class);
         mNightMode = mUiModeManager.getNightMode();
         mUiModeManager.setNightMode(MODE_NIGHT_YES);
+
+        mSpyContext = spy(mContext);
+        final SecureSettings secureSettings = TestUtils.mockSecureSettings();
         final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
-                mock(SecureSettings.class));
+                secureSettings);
         final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
-        mStubMenuViewAppearance = new MenuViewAppearance(mContext, stubWindowManager);
-        mMenuView = spy(new MenuView(mContext, stubMenuViewModel, mStubMenuViewAppearance));
-        mLastPosition = Prefs.getString(mContext,
+        mStubMenuViewAppearance = new MenuViewAppearance(mSpyContext, stubWindowManager);
+        mMenuView = spy(new MenuView(mSpyContext, stubMenuViewModel, mStubMenuViewAppearance,
+                secureSettings));
+        mLastPosition = Prefs.getString(mSpyContext,
                 Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, /* defaultValue= */ null);
     }
 
@@ -154,6 +166,25 @@
         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() {
+        // Notably, this shouldn't crash the settings app,
+        // because the button target args are configured.
+        mMenuView.gotoEditScreen();
+        verify(mSpyContext).startActivity(any());
+    }
+
     private InstantInsetLayerDrawable getMenuViewInsetLayer() {
         return (InstantInsetLayerDrawable) mMenuView.getBackground();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java
index 10c8caa..8399fa8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java
@@ -16,11 +16,27 @@
 
 package com.android.systemui.accessibility.utils;
 
-import android.os.SystemClock;
+import static com.android.internal.accessibility.common.ShortcutConstants.SERVICES_SEPARATOR;
 
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import com.android.systemui.util.settings.SecureSettings;
+
+import java.util.Set;
+import java.util.StringJoiner;
 import java.util.function.BooleanSupplier;
 
 public class TestUtils {
+    private static final ComponentName TEST_COMPONENT_A = new ComponentName("pkg", "A");
+    private static final ComponentName TEST_COMPONENT_B = new ComponentName("pkg", "B");
+    public static final String[] TEST_BUTTON_TARGETS = {
+            TEST_COMPONENT_A.flattenToString(), TEST_COMPONENT_B.flattenToString()};
     public static long DEFAULT_CONDITION_DURATION = 5_000;
 
     /**
@@ -55,4 +71,28 @@
             SystemClock.sleep(sleepMs);
         }
     }
+
+    /**
+     * Returns a mock secure settings configured to return information needed for tests.
+     * Currently, this only includes button targets.
+     */
+    public static SecureSettings mockSecureSettings() {
+        SecureSettings secureSettings = mock(SecureSettings.class);
+
+        final String targets = getShortcutTargets(
+                Set.of(TEST_COMPONENT_A, TEST_COMPONENT_B));
+        when(secureSettings.getStringForUser(
+                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
+                UserHandle.USER_CURRENT)).thenReturn(targets);
+
+        return secureSettings;
+    }
+
+    private static String getShortcutTargets(Set<ComponentName> components) {
+        final StringJoiner stringJoiner = new StringJoiner(String.valueOf(SERVICES_SEPARATOR));
+        for (ComponentName target : components) {
+            stringJoiner.add(target.flattenToString());
+        }
+        return stringJoiner.toString();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
index a47e288..7c03d78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -27,12 +27,13 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.keyguard.logging.KeyguardLogger
+import com.android.systemui.Flags
 import com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
-import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.deviceentry.domain.interactor.AuthRippleInteractor
 import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.LightRevealScrim
 import com.android.systemui.statusbar.NotificationShadeWindowController
@@ -42,7 +43,7 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.leak.RotationUtils
 import com.android.systemui.util.mockito.any
-import javax.inject.Provider
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import org.junit.After
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
@@ -61,8 +62,10 @@
 import org.mockito.MockitoAnnotations
 import org.mockito.MockitoSession
 import org.mockito.quality.Strictness
+import javax.inject.Provider
 
 
+@ExperimentalCoroutinesApi
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 class AuthRippleControllerTest : SysuiTestCase() {
@@ -74,6 +77,7 @@
     @Mock private lateinit var configurationController: ConfigurationController
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var authController: AuthController
+    @Mock private lateinit var authRippleInteractor: AuthRippleInteractor
     @Mock private lateinit var keyguardStateController: KeyguardStateController
     @Mock
     private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
@@ -88,8 +92,6 @@
     @Mock
     private lateinit var statusBarStateController: StatusBarStateController
     @Mock
-    private lateinit var featureFlags: FeatureFlags
-    @Mock
     private lateinit var lightRevealScrim: LightRevealScrim
     @Mock
     private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal
@@ -103,6 +105,7 @@
 
     @Before
     fun setUp() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
         MockitoAnnotations.initMocks(this)
         staticMockSession = mockitoSession()
                 .mockStatic(RotationUtils::class.java)
@@ -128,6 +131,7 @@
             KeyguardLogger(logcatLogBuffer(AuthRippleController.TAG)),
             biometricUnlockController,
             lightRevealScrim,
+            authRippleInteractor,
             facePropertyRepository,
             rippleView,
         )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 8127bb1..6a9c881 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -1226,6 +1226,7 @@
     @Test
     fun descriptionOverriddenByContentView() =
         runGenericTest(contentView = promptContentView, description = "test description") {
+            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
             val contentView by collectLastValue(viewModel.contentView)
             val description by collectLastValue(viewModel.description)
 
@@ -1236,6 +1237,7 @@
     @Test
     fun descriptionWithoutContentView() =
         runGenericTest(description = "test description") {
+            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
             val contentView by collectLastValue(viewModel.contentView)
             val description by collectLastValue(viewModel.description)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
index 0dfdeca..bdf0e06 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.deviceentry.data.repository
+package com.android.systemui.deviceentry.domain.interactor
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -23,12 +23,13 @@
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.biometrics.shared.model.SensorStrength
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryHapticsInteractor
 import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
 import com.android.systemui.keyevent.data.repository.fakeKeyEventRepository
 import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
 import com.android.systemui.kosmos.testScope
@@ -158,9 +159,10 @@
         }
 
     private suspend fun enterDeviceFromBiometricUnlock() {
-        kosmos.fakeDeviceEntryRepository.enteringDeviceFromBiometricUnlock(
+        kosmos.fakeKeyguardRepository.setBiometricUnlockSource(
             BiometricUnlockSource.FINGERPRINT_SENSOR
         )
+        kosmos.fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
     }
 
     private fun fingerprintFailure() {
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 8a3a434..1183964 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -25,6 +25,7 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
 import static com.android.systemui.Flags.FLAG_REFACTOR_GET_CURRENT_USER;
+import static com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR;
 import static com.android.systemui.keyguard.KeyguardViewMediator.DELAYED_KEYGUARD_ACTION;
 import static com.android.systemui.keyguard.KeyguardViewMediator.KEYGUARD_LOCK_AFTER_DELAY_DEFAULT;
 import static com.android.systemui.keyguard.KeyguardViewMediator.REBOOT_MAINLINE_UPDATE;
@@ -270,8 +271,8 @@
                 mSceneContainerFlags,
                 mKosmos::getCommunalInteractor);
         mFeatureFlags = new FakeFeatureFlags();
-        mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
         mSetFlagsRule.enableFlags(FLAG_REFACTOR_GET_CURRENT_USER);
+        mSetFlagsRule.disableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR);
 
         DejankUtils.setImmediate(true);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 4f3a63d..e93ad0be3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -21,6 +21,7 @@
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
 import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
@@ -29,7 +30,6 @@
 import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
 import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository
@@ -137,8 +137,8 @@
 
         whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN)
 
-        featureFlags = FakeFeatureFlags().apply { set(Flags.KEYGUARD_WM_STATE_REFACTOR, false) }
         mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
+        featureFlags = FakeFeatureFlags()
 
         keyguardInteractor = createKeyguardInteractor()
 
@@ -299,6 +299,10 @@
                     powerInteractor = powerInteractor,
                 )
                 .apply { start() }
+
+        mSetFlagsRule.disableFlags(
+            FLAG_KEYGUARD_WM_STATE_REFACTOR,
+        )
     }
 
     @Test
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 c864704..699284e 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
@@ -39,6 +39,7 @@
 import com.android.systemui.statusbar.VibratorHelper
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -71,6 +72,7 @@
             FakeFeatureFlagsClassic().apply { set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false) }
         underTest =
             DefaultDeviceEntrySection(
+                TestScope().backgroundScope,
                 keyguardUpdateMonitor,
                 authController,
                 windowManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
index f93d52b..aa54565 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
@@ -28,6 +28,7 @@
 import android.view.ViewConfiguration
 import android.view.WindowManager
 import androidx.test.filters.SmallTest
+import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.util.LatencyTracker
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.plugins.NavigationEdgeBackPlugin
@@ -40,8 +41,10 @@
 import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
+import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -59,12 +62,16 @@
     @Mock private lateinit var windowManager: WindowManager
     @Mock private lateinit var configurationController: ConfigurationController
     @Mock private lateinit var latencyTracker: LatencyTracker
+    @Mock private lateinit var interactionJankMonitor: InteractionJankMonitor
     @Mock private lateinit var layoutParams: WindowManager.LayoutParams
     @Mock private lateinit var backCallback: NavigationEdgeBackPlugin.BackCallback
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
+        `when`(interactionJankMonitor.begin(any(), anyInt())).thenReturn(true)
+        `when`(interactionJankMonitor.end(anyInt())).thenReturn(true)
+        `when`(interactionJankMonitor.cancel(anyInt())).thenReturn(true)
         mBackPanelController =
             BackPanelController(
                 context,
@@ -74,6 +81,7 @@
                 vibratorHelper,
                 configurationController,
                 latencyTracker,
+                interactionJankMonitor,
             )
         mBackPanelController.setLayoutParams(layoutParams)
         mBackPanelController.setBackCallback(backCallback)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt
new file mode 100644
index 0000000..e8aa8f0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt
@@ -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.systemui.qs.tileimpl
+
+import android.animation.AnimatorTestRule
+import android.content.Context
+import android.service.quicksettings.Tile
+import android.testing.AndroidTestingRunner
+import android.testing.UiThreadTest
+import android.view.ContextThemeWrapper
+import android.view.View
+import android.widget.ImageView
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.connectivity.WifiIcons
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import org.junit.Rule
+import org.junit.runner.RunWith
+
+/** Test for regression b/311121830 */
+@RunWith(AndroidTestingRunner::class)
+@UiThreadTest
+@SmallTest
+class QSIconViewImplTest_311121830 : SysuiTestCase() {
+
+    @get:Rule val animatorRule = AnimatorTestRule()
+
+    @Test
+    fun alwaysLastIcon() {
+        // Need to inflate with the correct theme so the colors can be retrieved and the animations
+        // are run
+        val iconView =
+            AnimateQSIconViewImpl(
+                ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
+            )
+
+        val initialState =
+            QSTile.State().apply {
+                state = Tile.STATE_INACTIVE
+                icon = QSTileImpl.ResourceIcon.get(R.drawable.ic_qs_no_internet_available)
+            }
+        val firstState =
+            QSTile.State().apply {
+                state = Tile.STATE_ACTIVE
+                icon = QSTileImpl.ResourceIcon.get(WifiIcons.WIFI_NO_INTERNET_ICONS[4])
+            }
+        val secondState =
+            QSTile.State().apply {
+                state = Tile.STATE_ACTIVE
+                icon = QSTileImpl.ResourceIcon.get(WifiIcons.WIFI_FULL_ICONS[4])
+            }
+
+        // Start with the initial state
+        iconView.setIcon(initialState, /* allowAnimations= */ false)
+
+        // Set the first state to animate, and advance time to half the time of the animation
+        iconView.setIcon(firstState, /* allowAnimations= */ true)
+        animatorRule.advanceTimeBy(QSIconViewImpl.QS_ANIM_LENGTH / 2)
+
+        // Set the second state to animate (it shouldn't, because `State.state` is the same) and
+        // advance time to 2 animations length
+        iconView.setIcon(secondState, /* allowAnimations= */ true)
+        animatorRule.advanceTimeBy(QSIconViewImpl.QS_ANIM_LENGTH * 2)
+
+        assertThat(iconView.mLastIcon).isEqualTo(secondState.icon)
+    }
+
+    private class AnimateQSIconViewImpl(context: Context) : QSIconViewImpl(context) {
+        override fun createIcon(): View {
+            return object : ImageView(context) {
+                override fun isShown(): Boolean {
+                    return true
+                }
+            }
+        }
+    }
+}
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 c7479fd5..1ed8c3c 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
@@ -32,6 +32,7 @@
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.recordissue.RecordIssueDialogDelegate
 import com.android.systemui.res.R
+import com.android.systemui.settings.UserContextProvider
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -65,6 +66,7 @@
     @Mock private lateinit var keyguardDismissUtil: KeyguardDismissUtil
     @Mock private lateinit var keyguardStateController: KeyguardStateController
     @Mock private lateinit var dialogLauncherAnimator: DialogLaunchAnimator
+    @Mock private lateinit var userContextProvider: UserContextProvider
     @Mock private lateinit var delegateFactory: RecordIssueDialogDelegate.Factory
     @Mock private lateinit var dialogDelegate: RecordIssueDialogDelegate
     @Mock private lateinit var dialog: SystemUIDialog
@@ -94,6 +96,7 @@
                 keyguardDismissUtil,
                 keyguardStateController,
                 dialogLauncherAnimator,
+                userContextProvider,
                 delegateFactory,
             )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index b24b877..c0ef50f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -1069,6 +1069,22 @@
         assertThat(mInternetDialogController.mCallback).isNull();
     }
 
+    @Test
+    public void hasActiveSubId_activeSubIdListIsEmpty_returnFalse() {
+        when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{});
+        mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged();
+
+        assertThat(mInternetDialogController.hasActiveSubId()).isFalse();
+    }
+
+    @Test
+    public void hasActiveSubId_activeSubIdListNotEmpty_returnTrue() {
+        when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID});
+        mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged();
+
+        assertThat(mInternetDialogController.hasActiveSubId()).isTrue();
+    }
+
     private String getResourcesString(String name) {
         return mContext.getResources().getString(getResourcesId(name));
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
index 70a48f5..e9f2132 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
@@ -131,7 +131,10 @@
         whenever(packageManager.resolveServiceAsUser(any(), anyInt(), anyInt()))
             .thenReturn(mock(ResolveInfo::class.java))
 
-        featureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
+        mSetFlagsRule.disableFlags(
+            com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
+        )
+
         subject =
             OverviewProxyService(
                 context,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
index 58eec2e..4519ba6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
@@ -65,6 +65,7 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider;
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.collection.render.NotifViewBarn;
 import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager;
 import com.android.systemui.util.settings.SecureSettings;
@@ -111,6 +112,7 @@
     @Spy private FakeNotifInflater mNotifInflater = new FakeNotifInflater();
     private final SectionStyleProvider mSectionStyleProvider = new SectionStyleProvider();
     @Mock private UserTracker mUserTracker;
+    @Mock private GroupMembershipManager mGroupMembershipManager;
 
     private NotifUiAdjustmentProvider mAdjustmentProvider;
 
@@ -127,7 +129,9 @@
                 mSecureSettings,
                 mLockscreenUserManager,
                 mSectionStyleProvider,
-                mUserTracker);
+                mUserTracker,
+                mGroupMembershipManager
+                );
         mEntry = getNotificationEntryBuilder().setParent(ROOT_ENTRY).build();
         mInflationError = new Exception(TEST_MESSAGE);
         mErrorManager = new NotifInflationErrorManager();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
index f9f8d8a..73c49c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
@@ -17,6 +17,8 @@
 
 import android.database.ContentObserver
 import android.os.Handler
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.provider.Settings.Secure.SHOW_NOTIFICATION_SNOOZE
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
@@ -28,6 +30,8 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
 import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
 import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
@@ -35,6 +39,8 @@
 import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.util.settings.SecureSettings
 import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -55,6 +61,7 @@
     private val uri = FakeSettings().getUriFor(SHOW_NOTIFICATION_SNOOZE)
     private val dirtyListener: Runnable = mock()
     private val userTracker: UserTracker = mock()
+    private val groupMembershipManager: GroupMembershipManager = mock()
 
     private val section = NotifSection(mock(), 0)
     private val entry = NotificationEntryBuilder()
@@ -69,7 +76,8 @@
         secureSettings,
         lockscreenUserManager,
         sectionStyleProvider,
-        userTracker
+        userTracker,
+        groupMembershipManager,
     )
 
     @Before
@@ -127,4 +135,42 @@
         assertThat(withSnoozing.isSnoozeEnabled).isTrue()
         assertThat(withSnoozing).isNotEqualTo(original)
     }
+
+    @Test
+    @EnableFlags(AsyncHybridViewInflation.FLAG_NAME)
+    fun changeIsChildInGroup_asyncHybirdFlagEnabled_needReInflation() {
+        // Given: an Entry that is not child in group
+        // AsyncHybridViewInflation flag is enabled
+        whenever(groupMembershipManager.isChildInGroup(entry)).thenReturn(false)
+        val oldAdjustment = adjustmentProvider.calculateAdjustment(entry)
+        assertThat(oldAdjustment.isChildInGroup).isFalse()
+
+        // When: the Entry becomes a group child
+        whenever(groupMembershipManager.isChildInGroup(entry)).thenReturn(true)
+        val newAdjustment = adjustmentProvider.calculateAdjustment(entry)
+        assertThat(newAdjustment.isChildInGroup).isTrue()
+        assertThat(newAdjustment).isNotEqualTo(oldAdjustment)
+
+        // Then: need re-inflation
+        assertTrue(NotifUiAdjustment.needReinflate(oldAdjustment, newAdjustment))
+    }
+
+    @Test
+    @DisableFlags(AsyncHybridViewInflation.FLAG_NAME)
+    fun changeIsChildInGroup_asyncHybirdFlagDisabled_noNeedForReInflation() {
+        // Given: an Entry that is not child in group
+        // AsyncHybridViewInflation flag is disabled
+        whenever(groupMembershipManager.isChildInGroup(entry)).thenReturn(false)
+        val oldAdjustment = adjustmentProvider.calculateAdjustment(entry)
+        assertThat(oldAdjustment.isChildInGroup).isFalse()
+
+        // When: the Entry becomes a group child
+        whenever(groupMembershipManager.isChildInGroup(entry)).thenReturn(true)
+        val newAdjustment = adjustmentProvider.calculateAdjustment(entry)
+        assertThat(newAdjustment.isChildInGroup).isTrue()
+        assertThat(newAdjustment).isNotEqualTo(oldAdjustment)
+
+        // Then: need no re-inflation
+        assertFalse(NotifUiAdjustment.needReinflate(oldAdjustment, newAdjustment))
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt
index 3f7fc97..fd41921 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt
@@ -62,7 +62,7 @@
     fun onCreateView_noMatchingViewForName_returnNull() {
         // GIVEN we have ViewFactories that replaces TextViews in expanded and collapsed layouts
         val layoutType = FLAG_CONTENT_VIEW_EXPANDED
-        inflaterFactory = NotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
+        inflaterFactory = createNotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
 
         // WHEN we try to inflate an ImageView for the expanded layout
         val createdView = inflaterFactory.onCreateView("ImageView", context, attrs)
@@ -78,7 +78,7 @@
     fun onCreateView_noMatchingViewForLayoutType_returnNull() {
         // GIVEN we have ViewFactories that replaces TextViews in expanded and collapsed layouts
         val layoutType = FLAG_CONTENT_VIEW_HEADS_UP
-        inflaterFactory = NotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
+        inflaterFactory = createNotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
 
         // WHEN we try to inflate a TextView for the heads-up layout
         val createdView = inflaterFactory.onCreateView("TextView", context, attrs)
@@ -94,7 +94,7 @@
     fun onCreateView_matchingViews_returnReplacementView() {
         // GIVEN we have ViewFactories that replaces TextViews in expanded and collapsed layouts
         val layoutType = FLAG_CONTENT_VIEW_EXPANDED
-        inflaterFactory = NotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
+        inflaterFactory = createNotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
 
         // WHEN we try to inflate a TextView for the expanded layout
         val createdView = inflaterFactory.onCreateView("TextView", context, attrs)
@@ -110,7 +110,7 @@
         // GIVEN we have two factories that replaces TextViews in expanded layouts
         val layoutType = FLAG_CONTENT_VIEW_EXPANDED
         inflaterFactory =
-            NotifLayoutInflaterFactory(
+            createNotifLayoutInflaterFactory(
                 row,
                 layoutType,
                 setOf(
@@ -147,4 +147,18 @@
                     null
                 }
         }
+
+    private fun createNotifLayoutInflaterFactory(
+        row: ExpandableNotificationRow,
+        layoutType: Int,
+        notifRemoteViewsFactoryContainer: Set<NotifRemoteViewsFactory>
+    ) =
+        NotifLayoutInflaterFactory(
+            row,
+            layoutType,
+            object : NotifRemoteViewsFactoryContainer {
+                override val factories: Set<NotifRemoteViewsFactory> =
+                    notifRemoteViewsFactoryContainer
+            }
+        )
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index b0996ad..a0d1075 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -88,6 +88,8 @@
     private Notification.Builder mBuilder;
     private ExpandableNotificationRow mRow;
 
+    private NotificationTestHelper mHelper;
+
     @Mock private NotifRemoteViewCache mCache;
     @Mock private ConversationNotificationProcessor mConversationNotificationProcessor;
     @Mock private InflatedSmartReplyState mInflatedSmartReplyState;
@@ -119,11 +121,11 @@
                 .setContentTitle("Title")
                 .setContentText("Text")
                 .setStyle(new Notification.BigTextStyle().bigText("big text"));
-        NotificationTestHelper helper = new NotificationTestHelper(
+        mHelper = new NotificationTestHelper(
                 mContext,
                 mDependency,
                 TestableLooper.get(this));
-        ExpandableNotificationRow row = helper.createRow(mBuilder.build());
+        ExpandableNotificationRow row = mHelper.createRow(mBuilder.build());
         mRow = spy(row);
         when(mNotifLayoutInflaterFactoryProvider.provide(any(), any()))
                 .thenReturn(mNotifLayoutInflaterFactory);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt
new file mode 100644
index 0000000..1c959af
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.statusbar.notification.row
+
+import android.app.Notification
+import android.app.Person
+import android.platform.test.annotations.EnableFlags
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_SINGLE_LINE
+import com.android.systemui.statusbar.notification.row.SingleLineViewInflater.inflateSingleLineViewHolder
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
+import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineConversationViewBinder
+import com.android.systemui.util.mockito.mock
+import kotlin.test.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class SingleLineConversationViewBinderTest : SysuiTestCase() {
+    private lateinit var notificationBuilder: Notification.Builder
+    private lateinit var helper: NotificationTestHelper
+
+    @Before
+    fun setUp() {
+        allowTestableLooperAsMainThread()
+        helper = NotificationTestHelper(context, mDependency, TestableLooper.get(this))
+        notificationBuilder = Notification.Builder(context, CHANNEL_ID)
+        notificationBuilder
+            .setSmallIcon(R.drawable.ic_corp_icon)
+            .setContentTitle(CONTENT_TITLE)
+            .setContentText(CONTENT_TEXT)
+    }
+
+    @Test
+    @EnableFlags(AsyncHybridViewInflation.FLAG_NAME)
+    fun bindGroupConversationSingleLineView() {
+        // GIVEN a row with a group conversation notification
+        val user =
+            Person.Builder()
+                //                .setIcon(Icon.createWithResource(mContext,
+                // R.drawable.ic_account_circle))
+                .setName(USER_NAME)
+                .build()
+        val style =
+            Notification.MessagingStyle(user)
+                .addMessage(MESSAGE_TEXT, System.currentTimeMillis(), user)
+                .addMessage(
+                    "How about lunch?",
+                    System.currentTimeMillis(),
+                    Person.Builder().setName("user2").build()
+                )
+                .setGroupConversation(true)
+        notificationBuilder.setStyle(style).setShortcutId(SHORTCUT_ID)
+        val notification = notificationBuilder.build()
+        val row = helper.createRow(notification)
+
+        val viewHolder =
+            inflateSingleLineViewHolder(
+                isConversation = true,
+                reinflateFlags = FLAG_CONTENT_VIEW_SINGLE_LINE,
+                entry = row.entry,
+                context = context,
+                logger = mock()
+            )
+                as HybridConversationNotificationView
+        val viewModel =
+            SingleLineViewInflater.inflateSingleLineViewModel(
+                notification = notification,
+                messagingStyle = style,
+                builder = notificationBuilder,
+                systemUiContext = context,
+            )
+        // WHEN: binds the viewHolder
+        SingleLineConversationViewBinder.bind(
+            viewModel,
+            viewHolder,
+        )
+
+        // THEN: the single-line conversation view should be bind with view model's corresponding
+        // fields
+        assertEquals(viewModel.titleText, viewHolder.titleView.text)
+        assertEquals(viewModel.contentText, viewHolder.textView.text)
+        assertEquals(
+            viewModel.conversationData?.conversationSenderName,
+            viewHolder.conversationSenderNameView.text
+        )
+    }
+
+    private companion object {
+        const val CHANNEL_ID = "CHANNEL_ID"
+        const val CONTENT_TITLE = "CONTENT_TITLE"
+        const val CONTENT_TEXT = "CONTENT_TEXT"
+        const val USER_NAME = "USER_NAME"
+        const val MESSAGE_TEXT = "MESSAGE_TEXT"
+        const val SHORTCUT_ID = "Shortcut"
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt
new file mode 100644
index 0000000..f0fc349
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.statusbar.notification.row
+
+import android.app.Notification
+import android.platform.test.annotations.EnableFlags
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_SINGLE_LINE
+import com.android.systemui.statusbar.notification.row.SingleLineViewInflater.inflateSingleLineViewHolder
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
+import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineViewBinder
+import com.android.systemui.util.mockito.mock
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class SingleLineViewBinderTest : SysuiTestCase() {
+    private lateinit var notificationBuilder: Notification.Builder
+    private lateinit var helper: NotificationTestHelper
+
+    @Before
+    fun setUp() {
+        allowTestableLooperAsMainThread()
+        helper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+        notificationBuilder = Notification.Builder(mContext, CHANNEL_ID)
+        notificationBuilder
+            .setSmallIcon(R.drawable.ic_corp_icon)
+            .setContentTitle(CONTENT_TITLE)
+            .setContentText(CONTENT_TEXT)
+    }
+
+    @Test
+    @EnableFlags(AsyncHybridViewInflation.FLAG_NAME)
+    fun bindNonConversationSingleLineView() {
+        // GIVEN: a row with bigText style notification
+        val style = Notification.BigTextStyle().bigText(CONTENT_TEXT)
+        notificationBuilder.setStyle(style)
+        val notification = notificationBuilder.build()
+        val row: ExpandableNotificationRow = helper.createRow(notification)
+
+        val viewHolder =
+            inflateSingleLineViewHolder(
+                isConversation = false,
+                reinflateFlags = FLAG_CONTENT_VIEW_SINGLE_LINE,
+                entry = row.entry,
+                context = context,
+                logger = mock()
+            )
+        val viewModel =
+            SingleLineViewInflater.inflateSingleLineViewModel(
+                notification = notification,
+                messagingStyle = null,
+                builder = notificationBuilder,
+                systemUiContext = context,
+            )
+
+        // WHEN: binds the viewHolder
+        SingleLineViewBinder.bind(viewModel, viewHolder)
+
+        // THEN: the single-line view should be bind with viewModel's title and content text
+        Assert.assertEquals(viewModel.titleText, viewHolder?.titleView?.text)
+        Assert.assertEquals(viewModel.contentText, viewHolder?.textView?.text)
+    }
+
+    private companion object {
+        const val CHANNEL_ID = "CHANNEL_ID"
+        const val CONTENT_TITLE = "A Cool New Feature"
+        const val CONTENT_TEXT = "Checkout out new feature!"
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt
new file mode 100644
index 0000000..b67153a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt
@@ -0,0 +1,463 @@
+/*
+ * 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.statusbar.notification.row
+
+import android.app.Notification
+import android.app.Person
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffXfermode
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
+import android.platform.test.annotations.EnableFlags
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.core.graphics.drawable.toBitmap
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.ConversationAvatar
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.FacePile
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleIcon
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleLineViewModel
+import kotlin.test.assertEquals
+import kotlin.test.assertIs
+import kotlin.test.assertIsNot
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+@EnableFlags(AsyncHybridViewInflation.FLAG_NAME)
+class SingleLineViewInflaterTest : SysuiTestCase() {
+    private lateinit var helper: NotificationTestHelper
+    // Non-group MessagingStyles only have firstSender
+    private lateinit var firstSender: Person
+    private lateinit var lastSender: Person
+    private lateinit var firstSenderIcon: Icon
+    private lateinit var lastSenderIcon: Icon
+    private var firstSenderIconDrawable: Drawable? = null
+    private var lastSenderIconDrawable: Drawable? = null
+    private val currentUser: Person? = null
+
+    private companion object {
+        const val FIRST_SENDER_NAME = "First Sender"
+        const val LAST_SENDER_NAME = "Second Sender"
+        const val LAST_MESSAGE = "How about lunch?"
+
+        const val CONVERSATION_TITLE = "The Sender Family"
+        const val CONTENT_TITLE = "A Cool Group"
+        const val CONTENT_TEXT = "This is an amazing group chat"
+
+        const val SHORTCUT_ID = "Shortcut"
+    }
+
+    @Before
+    fun setUp() {
+        helper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+        firstSenderIcon = Icon.createWithBitmap(getBitmap(context, R.drawable.ic_person))
+        firstSenderIconDrawable = firstSenderIcon.loadDrawable(context)
+        lastSenderIcon =
+            Icon.createWithBitmap(
+                getBitmap(context, com.android.internal.R.drawable.ic_account_circle)
+            )
+        lastSenderIconDrawable = lastSenderIcon.loadDrawable(context)
+        firstSender = Person.Builder().setName(FIRST_SENDER_NAME).setIcon(firstSenderIcon).build()
+        lastSender = Person.Builder().setName(LAST_SENDER_NAME).setIcon(lastSenderIcon).build()
+    }
+
+    @Test
+    fun createViewModelForNonConversationSingleLineView() {
+        // Given: a non-conversation notification
+        val notificationType = NonMessaging()
+        val notification = getNotification(NonMessaging())
+
+        // When: inflate the SingleLineViewModel
+        val singleLineViewModel = notification.makeSingleLineViewModel(notificationType)
+
+        // Then: the inflated SingleLineViewModel should be as expected
+        // conversationData: null, because it's not a conversation notification
+        assertEquals(SingleLineViewModel(CONTENT_TITLE, CONTENT_TEXT, null), singleLineViewModel)
+    }
+
+    @Test
+    fun createViewModelForNonGroupConversationNotification() {
+        // Given: a non-group conversation notification
+        val notificationType = OneToOneConversation()
+        val notification = getNotification(notificationType)
+
+        // When: inflate the SingleLineViewModel
+        val singleLineViewModel = notification.makeSingleLineViewModel(notificationType)
+
+        // Then: the inflated SingleLineViewModel should be as expected
+        // titleText: Notification.ConversationTitle
+        // contentText: the last message text
+        // conversationSenderName: null, because it's not a group conversation
+        // conversationData.avatar: a single icon of the last sender
+        assertEquals(CONVERSATION_TITLE, singleLineViewModel.titleText)
+        assertEquals(LAST_MESSAGE, singleLineViewModel.contentText)
+        assertNull(
+            singleLineViewModel.conversationData?.conversationSenderName,
+            "Sender name should be null for one-on-one conversation"
+        )
+        assertTrue {
+            singleLineViewModel.conversationData
+                ?.avatar
+                ?.equalsTo(SingleIcon(firstSenderIcon.loadDrawable(context))) == true
+        }
+    }
+
+    @Test
+    fun createViewModelForNonGroupLegacyMessagingStyleNotification() {
+        // Given: a non-group legacy messaging style notification
+        val notificationType = LegacyMessaging()
+        val notification = getNotification(notificationType)
+
+        // When: inflate the SingleLineViewModel
+        val singleLineViewModel = notification.makeSingleLineViewModel(notificationType)
+
+        // Then: the inflated SingleLineViewModel should be as expected
+        // titleText: CONVERSATION_TITLE: SENDER_NAME
+        // contentText: the last message text
+        // conversationData: null, because it's not a conversation notification
+        assertEquals("$CONVERSATION_TITLE: $FIRST_SENDER_NAME", singleLineViewModel.titleText)
+        assertEquals(LAST_MESSAGE, singleLineViewModel.contentText)
+        assertNull(
+            singleLineViewModel.conversationData,
+            "conversationData should be null for legacy messaging conversation"
+        )
+    }
+
+    @Test
+    fun createViewModelForGroupLegacyMessagingStyleNotification() {
+        // Given: a non-group legacy messaging style notification
+        val notificationType = LegacyMessagingGroup()
+        val notification = getNotification(notificationType)
+
+        // When: inflate the SingleLineViewModel
+        val singleLineViewModel = notification.makeSingleLineViewModel(notificationType)
+
+        // Then: the inflated SingleLineViewModel should be as expected
+        // titleText: CONVERSATION_TITLE: LAST_SENDER_NAME
+        // contentText: the last message text
+        // conversationData: null, because it's not a conversation notification
+        assertEquals("$CONVERSATION_TITLE: $LAST_SENDER_NAME", singleLineViewModel.titleText)
+        assertEquals(LAST_MESSAGE, singleLineViewModel.contentText)
+        assertNull(
+            singleLineViewModel.conversationData,
+            "conversationData should be null for legacy messaging conversation"
+        )
+    }
+
+    @Test
+    fun createViewModelForNonGroupConversationNotificationWithShortcutIcon() {
+        // Given: a non-group conversation notification with a shortcut icon
+        val shortcutIcon =
+            Icon.createWithResource(context, com.android.internal.R.drawable.ic_account_circle)
+        val notificationType = OneToOneConversation(shortcutIcon = shortcutIcon)
+        val notification = getNotification(notificationType)
+
+        // When: inflate the SingleLineViewModel
+        val singleLineViewModel = notification.makeSingleLineViewModel(notificationType)
+
+        // Then: the inflated SingleLineViewModel should be expected
+        // titleText: Notification.ConversationTitle
+        // contentText: the last message text
+        // conversationSenderName: null, because it's not a group conversation
+        // conversationData.avatar: a single icon of the shortcut icon
+        assertEquals(CONVERSATION_TITLE, singleLineViewModel.titleText)
+        assertEquals(LAST_MESSAGE, singleLineViewModel.contentText)
+        assertNull(
+            singleLineViewModel.conversationData?.conversationSenderName,
+            "Sender name should be null for one-on-one conversation"
+        )
+        assertTrue {
+            singleLineViewModel.conversationData
+                ?.avatar
+                ?.equalsTo(SingleIcon(shortcutIcon.loadDrawable(context))) == true
+        }
+    }
+
+    @Test
+    fun createViewModelForGroupConversationNotificationWithLargeIcon() {
+        // Given: a group conversation notification with a large icon
+        val largeIcon =
+            Icon.createWithResource(context, com.android.internal.R.drawable.ic_account_circle)
+        val notificationType = GroupConversation(largeIcon = largeIcon)
+        val notification = getNotification(notificationType)
+
+        // When: inflate the SingleLineViewModel
+        val singleLineViewModel = notification.makeSingleLineViewModel(notificationType)
+
+        // Then: the inflated SingleLineViewModel should be expected
+        // titleText: Notification.ConversationTitle
+        // contentText: the last message text
+        // conversationSenderName: the last non-user sender's name
+        // conversationData.avatar: a single icon
+        assertEquals(CONVERSATION_TITLE, singleLineViewModel.titleText)
+        assertEquals(LAST_MESSAGE, singleLineViewModel.contentText)
+        assertEquals(
+            context.resources.getString(
+                com.android.internal.R.string.conversation_single_line_name_display,
+                LAST_SENDER_NAME
+            ),
+            singleLineViewModel.conversationData?.conversationSenderName
+        )
+        assertTrue {
+            singleLineViewModel.conversationData
+                ?.avatar
+                ?.equalsTo(SingleIcon(largeIcon.loadDrawable(context))) == true
+        }
+    }
+
+    @Test
+    fun createViewModelForGroupConversationWithNoIcon() {
+        // Given: a group conversation notification
+        val notificationType = GroupConversation()
+        val notification = getNotification(notificationType)
+
+        // When: inflate the SingleLineViewModel
+        val singleLineViewModel = notification.makeSingleLineViewModel(notificationType)
+
+        // Then: the inflated SingleLineViewModel should be expected
+        // titleText: Notification.ConversationTitle
+        // contentText: the last message text
+        // conversationSenderName: the last non-user sender's name
+        // conversationData.avatar: a face-pile consists the last sender's icon
+        assertEquals(CONVERSATION_TITLE, singleLineViewModel.titleText)
+        assertEquals(LAST_MESSAGE, singleLineViewModel.contentText)
+        assertEquals(
+            context.resources.getString(
+                com.android.internal.R.string.conversation_single_line_name_display,
+                LAST_SENDER_NAME
+            ),
+            singleLineViewModel.conversationData?.conversationSenderName
+        )
+
+        val backgroundColor =
+            Notification.Builder.recoverBuilder(context, notification)
+                .getBackgroundColor(/* isHeader = */ false)
+        assertTrue {
+            singleLineViewModel.conversationData
+                ?.avatar
+                ?.equalsTo(
+                    FacePile(
+                        firstSenderIconDrawable,
+                        lastSenderIconDrawable,
+                        backgroundColor,
+                    )
+                ) == true
+        }
+    }
+
+    sealed class NotificationType(val largeIcon: Icon? = null)
+
+    class NonMessaging(largeIcon: Icon? = null) : NotificationType(largeIcon)
+
+    class LegacyMessaging(largeIcon: Icon? = null) : NotificationType(largeIcon)
+
+    class LegacyMessagingGroup(largeIcon: Icon? = null) : NotificationType(largeIcon)
+
+    class OneToOneConversation(largeIcon: Icon? = null, val shortcutIcon: Icon? = null) :
+        NotificationType(largeIcon)
+
+    class GroupConversation(largeIcon: Icon? = null) : NotificationType(largeIcon)
+
+    private fun getNotification(type: NotificationType): Notification {
+        val notificationBuilder: Notification.Builder =
+            Notification.Builder(mContext, "channelId")
+                .setSmallIcon(R.drawable.ic_person)
+                .setContentTitle(CONTENT_TITLE)
+                .setContentText(CONTENT_TEXT)
+                .setLargeIcon(type.largeIcon)
+
+        val user = Person.Builder().setName("User").build()
+
+        val buildMessagingStyle =
+            Notification.MessagingStyle(user)
+                .setConversationTitle(CONVERSATION_TITLE)
+                .addMessage("Hi", 0, currentUser)
+
+        return when (type) {
+            is NonMessaging ->
+                notificationBuilder
+                    .setStyle(Notification.BigTextStyle().bigText("Big Text"))
+                    .build()
+            is LegacyMessaging -> {
+                buildMessagingStyle
+                    .addMessage("What's up?", 0, firstSender)
+                    .addMessage("Not much", 0, currentUser)
+                    .addMessage(LAST_MESSAGE, 0, firstSender)
+
+                val notification = notificationBuilder.setStyle(buildMessagingStyle).build()
+
+                assertNull(notification.shortcutId)
+                notification
+            }
+            is LegacyMessagingGroup -> {
+                buildMessagingStyle
+                    .addMessage("What's up?", 0, firstSender)
+                    .addMessage("Check out my new hover board!", 0, lastSender)
+                    .setGroupConversation(true)
+                    .addMessage(LAST_MESSAGE, 0, lastSender)
+
+                val notification = notificationBuilder.setStyle(buildMessagingStyle).build()
+
+                assertNull(notification.shortcutId)
+                notification
+            }
+            is OneToOneConversation -> {
+                buildMessagingStyle
+                    .addMessage("What's up?", 0, firstSender)
+                    .addMessage("Not much", 0, currentUser)
+                    .addMessage(LAST_MESSAGE, 0, firstSender)
+                    .setShortcutIcon(type.shortcutIcon)
+                notificationBuilder.setShortcutId(SHORTCUT_ID).setStyle(buildMessagingStyle).build()
+            }
+            is GroupConversation -> {
+                buildMessagingStyle
+                    .addMessage("What's up?", 0, firstSender)
+                    .addMessage("Check out my new hover board!", 0, lastSender)
+                    .setGroupConversation(true)
+                    .addMessage(LAST_MESSAGE, 0, lastSender)
+                notificationBuilder.setShortcutId(SHORTCUT_ID).setStyle(buildMessagingStyle).build()
+            }
+        }
+    }
+
+    private fun Notification.makeSingleLineViewModel(type: NotificationType): SingleLineViewModel {
+        val builder = Notification.Builder.recoverBuilder(context, this)
+
+        // Validate the recovered builder has the right type of style
+        val expectMessagingStyle =
+            when (type) {
+                is LegacyMessaging,
+                is LegacyMessagingGroup,
+                is OneToOneConversation,
+                is GroupConversation -> true
+                else -> false
+            }
+        if (expectMessagingStyle) {
+            assertIs<Notification.MessagingStyle>(
+                builder.style,
+                "Notification style should be MessagingStyle"
+            )
+        } else {
+            assertIsNot<Notification.MessagingStyle>(
+                builder.style,
+                message = "Notification style should not be MessagingStyle"
+            )
+        }
+
+        // Inflate the SingleLineViewModel
+        // Mock the behavior of NotificationContentInflater.doInBackground
+        val messagingStyle = builder.getMessagingStyle()
+        val isConversation = type is OneToOneConversation || type is GroupConversation
+        return SingleLineViewInflater.inflateSingleLineViewModel(
+            this,
+            if (isConversation) messagingStyle else null,
+            builder,
+            context
+        )
+    }
+
+    private fun Notification.Builder.getMessagingStyle(): Notification.MessagingStyle? {
+        return style as? Notification.MessagingStyle
+    }
+
+    private fun getBitmap(context: Context, resId: Int): Bitmap {
+        val largeIconDimension =
+            context.resources.getDimension(R.dimen.conversation_single_line_avatar_size)
+        val d = context.resources.getDrawable(resId)
+        val b =
+            Bitmap.createBitmap(
+                largeIconDimension.toInt(),
+                largeIconDimension.toInt(),
+                Bitmap.Config.ARGB_8888
+            )
+        val c = Canvas(b)
+        val paint = Paint()
+        c.drawCircle(
+            largeIconDimension / 2,
+            largeIconDimension / 2,
+            largeIconDimension.coerceAtMost(largeIconDimension) / 2,
+            paint
+        )
+        d.setBounds(0, 0, largeIconDimension.toInt(), largeIconDimension.toInt())
+        paint.setXfermode(PorterDuffXfermode(PorterDuff.Mode.SRC_IN))
+        c.saveLayer(0F, 0F, largeIconDimension, largeIconDimension, paint, Canvas.ALL_SAVE_FLAG)
+        d.draw(c)
+        c.restore()
+        return b
+    }
+
+    fun ConversationAvatar.equalsTo(other: ConversationAvatar?): Boolean =
+        when {
+            this === other -> true
+            this is SingleIcon && other is SingleIcon -> equalsTo(other)
+            this is FacePile && other is FacePile -> equalsTo(other)
+            else -> false
+        }
+
+    private fun SingleIcon.equalsTo(other: SingleIcon): Boolean =
+        iconDrawable?.equalsTo(other.iconDrawable) == true
+
+    private fun FacePile.equalsTo(other: FacePile): Boolean =
+        when {
+            bottomBackgroundColor != other.bottomBackgroundColor -> false
+            topIconDrawable?.equalsTo(other.topIconDrawable) != true -> false
+            bottomIconDrawable?.equalsTo(other.bottomIconDrawable) != true -> false
+            else -> true
+        }
+
+    fun Drawable.equalsTo(other: Drawable?): Boolean =
+        when {
+            this === other -> true
+            this.pixelsEqualTo(other) -> true
+            else -> false
+        }
+
+    private fun <T : Drawable> T.pixelsEqualTo(t: T?) =
+        toBitmap().pixelsEqualTo(t?.toBitmap(), false)
+
+    private fun Bitmap.pixelsEqualTo(otherBitmap: Bitmap?, shouldRecycle: Boolean = false) =
+        otherBitmap?.let { other ->
+            if (width == other.width && height == other.height) {
+                val res = toPixels().contentEquals(other.toPixels())
+                if (shouldRecycle) {
+                    doRecycle().also { otherBitmap.doRecycle() }
+                }
+                res
+            } else false
+        }
+            ?: kotlin.run { false }
+
+    private fun Bitmap.toPixels() =
+        IntArray(width * height).apply { getPixels(this, 0, width, 0, 0, width, height) }
+
+    fun Bitmap.doRecycle() {
+        if (!isRecycled) recycle()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 1ab4c32..dbe63f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -860,9 +860,6 @@
         when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(true);
         mController.getNotifStackController().setNotifStats(NotifStats.getEmpty());
 
-        // WHEN: call updateImportantForAccessibility
-        mController.updateImportantForAccessibility();
-
         // THEN: mNotificationStackScrollLayout should not be important for A11y
         verify(mNotificationStackScrollLayout)
                 .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
@@ -884,9 +881,6 @@
                         /* hasClearableSilentNotifs = */ false)
         );
 
-        // WHEN: call updateImportantForAccessibility
-        mController.updateImportantForAccessibility();
-
         // THEN: mNotificationStackScrollLayout should be important for A11y
         verify(mNotificationStackScrollLayout)
                 .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
@@ -908,9 +902,6 @@
                         /* hasClearableSilentNotifs = */ false)
         );
 
-        // WHEN: call updateImportantForAccessibility
-        mController.updateImportantForAccessibility();
-
         // THEN: mNotificationStackScrollLayout should be important for A11y
         verify(mNotificationStackScrollLayout)
                 .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
@@ -925,9 +916,6 @@
         when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(false);
         mController.getNotifStackController().setNotifStats(NotifStats.getEmpty());
 
-        // WHEN: call updateImportantForAccessibility
-        mController.updateImportantForAccessibility();
-
         // THEN: mNotificationStackScrollLayout should be important for A11y
         verify(mNotificationStackScrollLayout)
                 .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 06298b7..32c727c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -38,6 +38,9 @@
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
+import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
+import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
 import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
@@ -45,6 +48,7 @@
 import com.android.systemui.shade.mockLargeScreenHeaderHelper
 import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
 import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -55,15 +59,22 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class SharedNotificationContainerViewModelTest : SysuiTestCase() {
+    val aodBurnInViewModel = mock(AodBurnInViewModel::class.java)
+    lateinit var translationYFlow: MutableStateFlow<Float>
 
     val kosmos =
         testKosmos().apply {
             fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
         }
+
+    init {
+        kosmos.aodBurnInViewModel = aodBurnInViewModel
+    }
     val testScope = kosmos.testScope
     val configurationRepository = kosmos.fakeConfigurationRepository
     val keyguardRepository = kosmos.fakeKeyguardRepository
@@ -75,11 +86,14 @@
     val sharedNotificationContainerInteractor = kosmos.sharedNotificationContainerInteractor
     val largeScreenHeaderHelper = kosmos.mockLargeScreenHeaderHelper
 
-    val underTest = kosmos.sharedNotificationContainerViewModel
+    lateinit var underTest: SharedNotificationContainerViewModel
 
     @Before
     fun setUp() {
         overrideResource(R.bool.config_use_split_notification_shade, false)
+        translationYFlow = MutableStateFlow(0f)
+        whenever(aodBurnInViewModel.translationY(any())).thenReturn(translationYFlow)
+        underTest = kosmos.sharedNotificationContainerViewModel
     }
 
     @Test
@@ -579,9 +593,21 @@
         }
 
     @Test
+    fun translationYUpdatesOnKeyguardForBurnIn() =
+        testScope.runTest {
+            val translationY by collectLastValue(underTest.translationY(BurnInParameters()))
+
+            showLockscreen()
+            assertThat(translationY).isEqualTo(0)
+
+            translationYFlow.value = 150f
+            assertThat(translationY).isEqualTo(150f)
+        }
+
+    @Test
     fun translationYUpdatesOnKeyguard() =
         testScope.runTest {
-            val translationY by collectLastValue(underTest.translationY)
+            val translationY by collectLastValue(underTest.translationY(BurnInParameters()))
 
             configurationRepository.setDimensionPixelSize(
                 R.dimen.keyguard_translate_distance_on_swipe_up,
@@ -601,7 +627,7 @@
     @Test
     fun translationYDoesNotUpdateWhenShadeIsExpanded() =
         testScope.runTest {
-            val translationY by collectLastValue(underTest.translationY)
+            val translationY by collectLastValue(underTest.translationY(BurnInParameters()))
 
             configurationRepository.setDimensionPixelSize(
                 R.dimen.keyguard_translate_distance_on_swipe_up,
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 8dde935..cb45315 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
@@ -182,8 +182,10 @@
         when(mBouncerViewDelegate.getBackCallback()).thenReturn(mBouncerViewDelegateBackCallback);
         mFeatureFlags = new FakeFeatureFlags();
         mFeatureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false);
-        mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
-        mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR);
+        mSetFlagsRule.disableFlags(
+                com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR,
+                com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
+        );
 
         when(mNotificationShadeWindowController.getWindowRootView())
                 .thenReturn(mNotificationShadeWindowView);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
index 0e0d489..5b5819d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
@@ -125,22 +125,6 @@
         }
 
     @Test
-    fun satelliteManagerThrows_doesNotCrash() =
-        testScope.runTest {
-            setupDefaultRepo()
-
-            whenever(satelliteManager.registerForNtnSignalStrengthChanged(any(), any()))
-                .thenThrow(SatelliteException(13))
-
-            val conn by collectLastValue(underTest.connectionState)
-            val strength by collectLastValue(underTest.signalStrength)
-
-            // Flows have not emitted, we haven't crashed
-            assertThat(conn).isNull()
-            assertThat(strength).isNull()
-        }
-
-    @Test
     fun connectionState_mapsFromSatelliteModemState() =
         testScope.runTest {
             setupDefaultRepo()
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 b58a41c..457acd2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -190,7 +190,7 @@
 
         mWakefulnessLifecycle.dispatchFinishedWakingUp();
         mThemeOverlayController.start();
-        verify(mUserTracker).addCallback(mUserTrackerCallback.capture(), eq(mMainExecutor));
+        verify(mUserTracker).addCallback(mUserTrackerCallback.capture(), eq(mBgExecutor));
         verify(mWallpaperManager).addOnColorsChangedListener(mColorsListener.capture(), eq(null),
                 eq(UserHandle.USER_ALL));
         verify(mBroadcastDispatcher).registerReceiver(mBroadcastReceiver.capture(), any(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityQsShortcutsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityQsShortcutsRepository.kt
new file mode 100644
index 0000000..e547da1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityQsShortcutsRepository.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.accessibility.data.repository
+
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+
+class FakeAccessibilityQsShortcutsRepository : AccessibilityQsShortcutsRepository {
+
+    private val targetsPerUser = mutableMapOf<Int, MutableSharedFlow<Set<String>>>()
+
+    override fun a11yQsShortcutTargets(userId: Int): SharedFlow<Set<String>> {
+        return getFlow(userId).asSharedFlow()
+    }
+
+    /**
+     * Set the a11y qs shortcut targets. In real world, the A11y QS Shortcut targets are set by the
+     * Settings app not in SysUi
+     */
+    suspend fun setA11yQsShortcutTargets(userId: Int, targets: Set<String>) {
+        getFlow(userId).emit(targets)
+    }
+
+    private fun getFlow(userId: Int): MutableSharedFlow<Set<String>> =
+        targetsPerUser.getOrPut(userId) { MutableSharedFlow(replay = 1) }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index e25e8c0..bc7e7af 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -39,13 +39,4 @@
     private fun onConfigured(id: Int, providerInfo: AppWidgetProviderInfo, priority: Int) {
         _communalWidgets.value += listOf(CommunalWidgetContentModel(id, providerInfo, priority))
     }
-
-    private var isHostActive = false
-    override fun updateAppWidgetHostActive(active: Boolean) {
-        isHostActive = active
-    }
-
-    fun isHostActive(): Boolean {
-        return isHostActive
-    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
index 6436a38..77caeaa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
@@ -16,25 +16,16 @@
 package com.android.systemui.deviceentry.data.repository
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import dagger.Binds
 import dagger.Module
 import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.asStateFlow
 
 /** Fake implementation of [DeviceEntryRepository] */
 @SysUISingleton
 class FakeDeviceEntryRepository @Inject constructor() : DeviceEntryRepository {
-    private val _enteringDeviceFromBiometricUnlock: MutableSharedFlow<BiometricUnlockSource> =
-        MutableSharedFlow()
-    override val enteringDeviceFromBiometricUnlock: Flow<BiometricUnlockSource> =
-        _enteringDeviceFromBiometricUnlock.asSharedFlow()
-
     private var isLockscreenEnabled = true
 
     private val _isBypassEnabled = MutableStateFlow(false)
@@ -62,10 +53,6 @@
     fun setBypassEnabled(isBypassEnabled: Boolean) {
         _isBypassEnabled.value = isBypassEnabled
     }
-
-    suspend fun enteringDeviceFromBiometricUnlock(sourceType: BiometricUnlockSource) {
-        _enteringDeviceFromBiometricUnlock.emit(sourceType)
-    }
 }
 
 @Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorKosmos.kt
new file mode 100644
index 0000000..3070cf4
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.deviceentry.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@OptIn(ExperimentalCoroutinesApi::class)
+val Kosmos.authRippleInteractor by
+    Kosmos.Fixture {
+        AuthRippleInteractor(
+            deviceEntrySourceInteractor = deviceEntrySourceInteractor,
+            deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
index de58ae5..878e385 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
@@ -30,7 +30,7 @@
 val Kosmos.deviceEntryHapticsInteractor by
     Kosmos.Fixture {
         DeviceEntryHapticsInteractor(
-            deviceEntryInteractor = deviceEntryInteractor,
+            deviceEntrySourceInteractor = deviceEntrySourceInteractor,
             deviceEntryFingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
             deviceEntryBiometricAuthInteractor = deviceEntryBiometricAuthInteractor,
             fingerprintPropertyRepository = fingerprintPropertyRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
index 8dcdd3a..0d1a31f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
 package com.android.systemui.deviceentry.domain.interactor
 
 import com.android.systemui.authentication.domain.interactor.authenticationInteractor
@@ -28,6 +26,7 @@
 import com.android.systemui.scene.shared.flag.sceneContainerFlags
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
+@ExperimentalCoroutinesApi
 val Kosmos.deviceEntryInteractor by
     Kosmos.Fixture {
         DeviceEntryInteractor(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorKosmos.kt
new file mode 100644
index 0000000..0b9ec92
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.deviceentry.domain.interactor
+
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.kosmos.Kosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+val Kosmos.deviceEntrySourceInteractor by
+    Kosmos.Fixture {
+        DeviceEntrySourceInteractor(
+            keyguardInteractor = keyguardInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 5766f7a..793e2d7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -65,7 +65,7 @@
     override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing
 
     private val _isKeyguardUnlocked = MutableStateFlow(false)
-    override val isKeyguardUnlocked: StateFlow<Boolean> = _isKeyguardUnlocked.asStateFlow()
+    override val isKeyguardDismissible: StateFlow<Boolean> = _isKeyguardUnlocked.asStateFlow()
 
     private val _isKeyguardOccluded = MutableStateFlow(false)
     override val isKeyguardOccluded: Flow<Boolean> = _isKeyguardOccluded
@@ -165,7 +165,7 @@
         _isKeyguardOccluded.value = isOccluded
     }
 
-    fun setKeyguardUnlocked(isUnlocked: Boolean) {
+    fun setKeyguardDismissible(isUnlocked: Boolean) {
         _isKeyguardUnlocked.value = isUnlocked
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt
index 35cfa89..a8f45b0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt
@@ -26,7 +26,7 @@
 import com.android.systemui.kosmos.Kosmos.Fixture
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
-val Kosmos.aodBurnInViewModel by Fixture {
+var Kosmos.aodBurnInViewModel by Fixture {
     AodBurnInViewModel(
         burnInInteractor = burnInInteractor,
         configurationInteractor = configurationInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
index 5ceefde..73fd999 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntrySourceInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.burnInInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -27,6 +28,7 @@
 import com.android.systemui.scene.shared.flag.sceneContainerFlags
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 val Kosmos.fakeDeviceEntryIconViewModelTransition by Fixture { FakeDeviceEntryIconTransition() }
 
@@ -34,6 +36,7 @@
     setOf<DeviceEntryIconTransition>(fakeDeviceEntryIconViewModelTransition)
 }
 
+@ExperimentalCoroutinesApi
 val Kosmos.deviceEntryIconViewModel by Fixture {
     DeviceEntryIconViewModel(
         transitions = deviceEntryIconViewModelTransitionsMock,
@@ -46,5 +49,6 @@
         sceneContainerFlags = sceneContainerFlags,
         keyguardViewController = { statusBarKeyguardViewManager },
         deviceEntryInteractor = deviceEntryInteractor,
+        deviceEntrySourceInteractor = deviceEntrySourceInteractor,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index db40509..7c398cd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
 import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGlanceableHubTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.lockscreenToOccludedTransitionViewModel
@@ -40,6 +41,7 @@
         occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel,
         lockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel,
         glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel,
-        lockscreenToGlanceableHubTransitionViewModel = lockscreenToGlanceableHubTransitionViewModel
+        lockscreenToGlanceableHubTransitionViewModel = lockscreenToGlanceableHubTransitionViewModel,
+        aodBurnInViewModel = aodBurnInViewModel,
     )
 }
diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt
index f5e4af5..16f99e9 100644
--- a/ravenwood/framework-minus-apex-ravenwood-policies.txt
+++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt
@@ -6,6 +6,9 @@
 # Keep all feature flag implementations
 class :feature_flags stubclass
 
+# Keep all sysprops generated code implementations
+class :sysprops stubclass
+
 # Collections
 class android.util.ArrayMap stubclass
 class android.util.ArraySet stubclass
@@ -112,6 +115,12 @@
 class android.os.PatternMatcher stubclass
 class android.os.ParcelUuid stubclass
 
+# Logging related interfaces from modules-utils
+class com.android.internal.logging.InstanceId stubclass
+class com.android.internal.logging.InstanceIdSequence stubclass
+class com.android.internal.logging.UiEvent stubclass
+class com.android.internal.logging.UiEventLogger stubclass
+
 # XML
 class com.android.internal.util.XmlPullParserWrapper stubclass
 class com.android.internal.util.XmlSerializerWrapper stubclass
@@ -129,6 +138,9 @@
 class android.net.Uri stubclass
 class android.net.UriCodec stubclass
 
+# Telephony
+class android.telephony.PinResult stubclass
+
 # Just enough to support mocking, no further functionality
 class android.content.Context stub
     method <init> ()V stub
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index eacdc2f..91c522e 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -19,8 +19,6 @@
 import android.os.HandlerThread;
 import android.os.Looper;
 
-import java.util.Objects;
-
 public class RavenwoodRuleImpl {
     private static final String MAIN_THREAD_NAME = "RavenwoodMain";
 
@@ -31,6 +29,10 @@
     public static void init(RavenwoodRule rule) {
         android.os.Process.init$ravenwood(rule.mUid, rule.mPid);
         android.os.Binder.init$ravenwood();
+        android.os.SystemProperties.init$ravenwood(
+                rule.mSystemProperties.getValues(),
+                rule.mSystemProperties.getKeyReadablePredicate(),
+                rule.mSystemProperties.getKeyWritablePredicate());
 
         com.android.server.LocalServices.removeAllServicesForTest();
 
@@ -49,7 +51,8 @@
 
         com.android.server.LocalServices.removeAllServicesForTest();
 
-        android.os.Process.reset$ravenwood();
+        android.os.SystemProperties.reset$ravenwood();
         android.os.Binder.reset$ravenwood();
+        android.os.Process.reset$ravenwood();
     }
 }
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 53da8ba..dd442f0 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -62,6 +62,8 @@
 
     boolean mProvideMainThread = false;
 
+    final RavenwoodSystemProperties mSystemProperties = new RavenwoodSystemProperties();
+
     public RavenwoodRule() {
     }
 
@@ -98,6 +100,40 @@
             return this;
         }
 
+        /**
+         * Configure the given system property as immutable for the duration of the test.
+         * Read access to the key is allowed, and write access will fail. When {@code value} is
+         * {@code null}, the value is left as undefined.
+         *
+         * All properties in the {@code debug.*} namespace are automatically mutable, with no
+         * developer action required.
+         *
+         * Has no effect under non-Ravenwood environments.
+         */
+        public Builder setSystemPropertyImmutable(/* @NonNull */ String key,
+                /* @Nullable */ Object value) {
+            mRule.mSystemProperties.setValue(key, value);
+            mRule.mSystemProperties.setAccessReadOnly(key);
+            return this;
+        }
+
+        /**
+         * Configure the given system property as mutable for the duration of the test.
+         * Both read and write access to the key is allowed, and its value will be reset between
+         * each test. When {@code value} is {@code null}, the value is left as undefined.
+         *
+         * All properties in the {@code debug.*} namespace are automatically mutable, with no
+         * developer action required.
+         *
+         * Has no effect under non-Ravenwood environments.
+         */
+        public Builder setSystemPropertyMutable(/* @NonNull */ String key,
+                /* @Nullable */ Object value) {
+            mRule.mSystemProperties.setValue(key, value);
+            mRule.mSystemProperties.setAccessReadWrite(key);
+            return this;
+        }
+
         public RavenwoodRule build() {
             return mRule;
         }
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
new file mode 100644
index 0000000..85ad4e4
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
@@ -0,0 +1,175 @@
+/*
+ * 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.platform.test.ravenwood;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+
+class RavenwoodSystemProperties {
+    private final Map<String, String> mValues = new HashMap<>();
+
+    /** Set of additional keys that should be considered readable */
+    private final Set<String> mKeyReadable = new HashSet<>();
+    private final Predicate<String> mKeyReadablePredicate = (key) -> {
+        final String root = getKeyRoot(key);
+
+        if (root.startsWith("debug.")) return true;
+
+        // This set is carefully curated to help identify situations where a test may
+        // accidentally depend on a default value of an obscure property whose owner hasn't
+        // decided how Ravenwood should behave.
+        if (root.startsWith("boot.")) return true;
+        if (root.startsWith("build.")) return true;
+        if (root.startsWith("product.")) return true;
+        if (root.startsWith("soc.")) return true;
+        if (root.startsWith("system.")) return true;
+
+        switch (key) {
+            case "gsm.version.baseband":
+            case "no.such.thing":
+            case "ro.bootloader":
+            case "ro.debuggable":
+            case "ro.hardware":
+            case "ro.hw_timeout_multiplier":
+            case "ro.odm.build.media_performance_class":
+            case "ro.treble.enabled":
+            case "ro.vndk.version":
+                return true;
+        }
+
+        return mKeyReadable.contains(key);
+    };
+
+    /** Set of additional keys that should be considered writable */
+    private final Set<String> mKeyWritable = new HashSet<>();
+    private final Predicate<String> mKeyWritablePredicate = (key) -> {
+        final String root = getKeyRoot(key);
+
+        if (root.startsWith("debug.")) return true;
+
+        return mKeyWritable.contains(key);
+    };
+
+    public RavenwoodSystemProperties() {
+        // TODO: load these values from build.prop generated files
+        setValueForPartitions("product.brand", "Android");
+        setValueForPartitions("product.device", "Ravenwood");
+        setValueForPartitions("product.manufacturer", "Android");
+        setValueForPartitions("product.model", "Ravenwood");
+        setValueForPartitions("product.name", "Ravenwood");
+
+        setValueForPartitions("product.cpu.abilist", "x86_64");
+        setValueForPartitions("product.cpu.abilist32", "");
+        setValueForPartitions("product.cpu.abilist64", "x86_64");
+
+        setValueForPartitions("build.date", "Thu Jan 01 00:00:00 GMT 2024");
+        setValueForPartitions("build.date.utc", "1704092400");
+        setValueForPartitions("build.id", "MAIN");
+        setValueForPartitions("build.tags", "dev-keys");
+        setValueForPartitions("build.type", "userdebug");
+        setValueForPartitions("build.version.all_codenames", "REL");
+        setValueForPartitions("build.version.codename", "REL");
+        setValueForPartitions("build.version.incremental", "userdebug.ravenwood.20240101");
+        setValueForPartitions("build.version.known_codenames", "REL");
+        setValueForPartitions("build.version.release", "14");
+        setValueForPartitions("build.version.release_or_codename", "VanillaIceCream");
+        setValueForPartitions("build.version.sdk", "34");
+
+        setValue("ro.board.first_api_level", "1");
+        setValue("ro.product.first_api_level", "1");
+
+        setValue("ro.soc.manufacturer", "Android");
+        setValue("ro.soc.model", "Ravenwood");
+
+        setValue("ro.debuggable", "1");
+    }
+
+    Map<String, String> getValues() {
+        return new HashMap<>(mValues);
+    }
+
+    Predicate<String> getKeyReadablePredicate() {
+        return mKeyReadablePredicate;
+    }
+
+    Predicate<String> getKeyWritablePredicate() {
+        return mKeyWritablePredicate;
+    }
+
+    private static final String[] PARTITIONS = {
+            "bootimage",
+            "odm",
+            "product",
+            "system",
+            "system_ext",
+            "vendor",
+            "vendor_dlkm",
+    };
+
+    /**
+     * Set the given property for all possible partitions where it could be defined. For
+     * example, the value of {@code ro.build.type} is typically also mirrored under
+     * {@code ro.system.build.type}, etc.
+     */
+    private void setValueForPartitions(String key, String value) {
+        setValue("ro." + key, value);
+        for (String partition : PARTITIONS) {
+            setValue("ro." + partition + "." + key, value);
+        }
+    }
+
+    public void setValue(String key, Object value) {
+        final String valueString = (value == null) ? null : String.valueOf(value);
+        if ((valueString == null) || valueString.isEmpty()) {
+            mValues.remove(key);
+        } else {
+            mValues.put(key, valueString);
+        }
+    }
+
+    public void setAccessNone(String key) {
+        mKeyReadable.remove(key);
+        mKeyWritable.remove(key);
+    }
+
+    public void setAccessReadOnly(String key) {
+        mKeyReadable.add(key);
+        mKeyWritable.remove(key);
+    }
+
+    public void setAccessReadWrite(String key) {
+        mKeyReadable.add(key);
+        mKeyWritable.add(key);
+    }
+
+    /**
+     * Return the "root" of the given property key, stripping away any modifier prefix such as
+     * {@code ro.} or {@code persist.}.
+     */
+    private static String getKeyRoot(String key) {
+        if (key.startsWith("ro.")) {
+            return key.substring(3);
+        } else if (key.startsWith("persist.")) {
+            return key.substring(8);
+        } else {
+            return key;
+        }
+    }
+}
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index ab2546b..eaf01a3 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -1,6 +1,10 @@
 # Only classes listed here can use the Ravenwood annotations.
 
+com.android.internal.display.BrightnessSynchronizer
 com.android.internal.util.ArrayUtils
+com.android.internal.logging.MetricsLogger
+com.android.internal.logging.testing.FakeMetricsLogger
+com.android.internal.logging.testing.UiEventLoggerFake
 com.android.internal.os.BatteryStatsHistory
 com.android.internal.os.BatteryStatsHistory$TraceDelegate
 com.android.internal.os.BatteryStatsHistory$VarintParceler
@@ -28,6 +32,7 @@
 android.util.MonthDisplayHelper
 android.util.RecurrenceRule
 android.util.RotationUtils
+android.util.Singleton
 android.util.Slog
 android.util.SparseDoubleArray
 android.util.SparseSetArray
@@ -47,6 +52,7 @@
 android.os.Binder
 android.os.Binder$IdentitySupplier
 android.os.Broadcaster
+android.os.Build
 android.os.BundleMerger
 android.os.ConditionVariable
 android.os.FileUtils
@@ -65,11 +71,14 @@
 android.os.Process
 android.os.ServiceSpecificException
 android.os.SystemClock
+android.os.SystemProperties
 android.os.ThreadLocalWorkSource
 android.os.TimestampedValue
+android.os.Trace
 android.os.UidBatteryConsumer
 android.os.UidBatteryConsumer$Builder
 android.os.UserHandle
+android.os.UserManager
 android.os.WorkSource
 
 android.content.ClipData
@@ -83,16 +92,22 @@
 android.content.IntentFilter
 android.content.UriMatcher
 
-android.content.pm.PackageInfo
-android.content.pm.ApplicationInfo
-android.content.pm.PackageItemInfo
-android.content.pm.ComponentInfo
 android.content.pm.ActivityInfo
-android.content.pm.ServiceInfo
+android.content.pm.ApplicationInfo
+android.content.pm.ComponentInfo
+android.content.pm.PackageInfo
+android.content.pm.PackageItemInfo
+android.content.pm.PackageManager$Flags
+android.content.pm.PackageManager$PackageInfoFlags
+android.content.pm.PackageManager$ApplicationInfoFlags
+android.content.pm.PackageManager$ComponentInfoFlags
+android.content.pm.PackageManager$ResolveInfoFlags
 android.content.pm.PathPermission
 android.content.pm.ProviderInfo
 android.content.pm.ResolveInfo
+android.content.pm.ServiceInfo
 android.content.pm.Signature
+android.content.pm.UserInfo
 
 android.database.AbstractCursor
 android.database.CharArrayBuffer
@@ -126,6 +141,12 @@
 
 android.content.ContentProvider
 
+android.metrics.LogMaker
+
+android.view.Display$HdrCapabilities
+android.view.Display$Mode
+android.view.DisplayInfo
+
 com.android.server.LocalServices
 com.android.server.power.stats.BatteryStatsImpl
 
diff --git a/services/backup/flags.aconfig b/services/backup/flags.aconfig
index 4022e33..1416c88 100644
--- a/services/backup/flags.aconfig
+++ b/services/backup/flags.aconfig
@@ -11,7 +11,7 @@
 
 flag {
     name: "enable_metrics_system_backup_agents"
-    namespace: "backup"
+    namespace: "onboarding"
     description: "Enable SystemBackupAgent to collect B&R agent metrics by passing an instance of "
             "the logger to each BackupHelper."
     bug: "296844513"
diff --git a/services/companion/TEST_MAPPING b/services/companion/TEST_MAPPING
index 37c47ba..ae6d591 100644
--- a/services/companion/TEST_MAPPING
+++ b/services/companion/TEST_MAPPING
@@ -9,5 +9,10 @@
     {
       "name": "CtsCompanionDeviceManagerNoCompanionServicesTestCases"
     }
+  ],
+  "postsubmit": [
+    {
+      "name": "CtsCompanionDeviceManagerMultiProcessTestCases"
+    }
   ]
 }
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 3b9d92d..8962bf0 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -163,7 +163,7 @@
         createDeviceInternal(InputDeviceDescriptor.TYPE_MOUSE, deviceName, vendorId, productId,
                 deviceToken, displayId, phys,
                 () -> mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys));
-        mInputManagerInternal.setVirtualMousePointerDisplayId(displayId);
+        setVirtualMousePointerDisplayId(displayId);
     }
 
     void createTouchscreen(@NonNull String deviceName, int vendorId, int productId,
@@ -235,8 +235,7 @@
         // id if there's another mouse (choose the most recent). The inputDeviceDescriptor must be
         // removed from the mInputDeviceDescriptors instance variable prior to this point.
         if (inputDeviceDescriptor.isMouse()) {
-            if (mInputManagerInternal.getVirtualMousePointerDisplayId()
-                    == inputDeviceDescriptor.getDisplayId()) {
+            if (getVirtualMousePointerDisplayId() == inputDeviceDescriptor.getDisplayId()) {
                 updateActivePointerDisplayIdLocked();
             }
         }
@@ -271,6 +270,7 @@
         mWindowManager.setDisplayImePolicy(displayId, policy);
     }
 
+    // TODO(b/293587049): Remove after pointer icon refactor is complete.
     @GuardedBy("mLock")
     private void updateActivePointerDisplayIdLocked() {
         InputDeviceDescriptor mostRecentlyCreatedMouse = null;
@@ -285,11 +285,11 @@
             }
         }
         if (mostRecentlyCreatedMouse != null) {
-            mInputManagerInternal.setVirtualMousePointerDisplayId(
+            setVirtualMousePointerDisplayId(
                     mostRecentlyCreatedMouse.getDisplayId());
         } else {
             // All mice have been unregistered
-            mInputManagerInternal.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY);
+            setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY);
         }
     }
 
@@ -349,10 +349,8 @@
             if (inputDeviceDescriptor == null) {
                 return false;
             }
-            if (inputDeviceDescriptor.getDisplayId()
-                    != mInputManagerInternal.getVirtualMousePointerDisplayId()) {
-                mInputManagerInternal.setVirtualMousePointerDisplayId(
-                        inputDeviceDescriptor.getDisplayId());
+            if (inputDeviceDescriptor.getDisplayId() != getVirtualMousePointerDisplayId()) {
+                setVirtualMousePointerDisplayId(inputDeviceDescriptor.getDisplayId());
             }
             return mNativeWrapper.writeButtonEvent(inputDeviceDescriptor.getNativePointer(),
                     event.getButtonCode(), event.getAction(), event.getEventTimeNanos());
@@ -380,10 +378,8 @@
             if (inputDeviceDescriptor == null) {
                 return false;
             }
-            if (inputDeviceDescriptor.getDisplayId()
-                    != mInputManagerInternal.getVirtualMousePointerDisplayId()) {
-                mInputManagerInternal.setVirtualMousePointerDisplayId(
-                        inputDeviceDescriptor.getDisplayId());
+            if (inputDeviceDescriptor.getDisplayId() != getVirtualMousePointerDisplayId()) {
+                setVirtualMousePointerDisplayId(inputDeviceDescriptor.getDisplayId());
             }
             return mNativeWrapper.writeRelativeEvent(inputDeviceDescriptor.getNativePointer(),
                     event.getRelativeX(), event.getRelativeY(), event.getEventTimeNanos());
@@ -397,10 +393,8 @@
             if (inputDeviceDescriptor == null) {
                 return false;
             }
-            if (inputDeviceDescriptor.getDisplayId()
-                    != mInputManagerInternal.getVirtualMousePointerDisplayId()) {
-                mInputManagerInternal.setVirtualMousePointerDisplayId(
-                        inputDeviceDescriptor.getDisplayId());
+            if (inputDeviceDescriptor.getDisplayId() != getVirtualMousePointerDisplayId()) {
+                setVirtualMousePointerDisplayId(inputDeviceDescriptor.getDisplayId());
             }
             return mNativeWrapper.writeScrollEvent(inputDeviceDescriptor.getNativePointer(),
                     event.getXAxisMovement(), event.getYAxisMovement(), event.getEventTimeNanos());
@@ -415,12 +409,11 @@
                 throw new IllegalArgumentException(
                         "Could not get cursor position for input device for given token");
             }
-            if (inputDeviceDescriptor.getDisplayId()
-                    != mInputManagerInternal.getVirtualMousePointerDisplayId()) {
-                mInputManagerInternal.setVirtualMousePointerDisplayId(
-                        inputDeviceDescriptor.getDisplayId());
+            if (inputDeviceDescriptor.getDisplayId() != getVirtualMousePointerDisplayId()) {
+                setVirtualMousePointerDisplayId(inputDeviceDescriptor.getDisplayId());
             }
-            return LocalServices.getService(InputManagerInternal.class).getCursorPosition();
+            return LocalServices.getService(InputManagerInternal.class).getCursorPosition(
+                    inputDeviceDescriptor.getDisplayId());
         }
     }
 
@@ -847,4 +840,22 @@
         /** Returns true if the calling thread is a valid thread for device creation. */
         boolean isValidThread();
     }
+
+    // TODO(b/293587049): Remove after pointer icon refactor is complete.
+    private void setVirtualMousePointerDisplayId(int displayId) {
+        if (com.android.input.flags.Flags.enablePointerChoreographer()) {
+            // We no longer need to set the pointer display when pointer choreographer is enabled.
+            return;
+        }
+        mInputManagerInternal.setVirtualMousePointerDisplayId(displayId);
+    }
+
+    // TODO(b/293587049): Remove after pointer icon refactor is complete.
+    private int getVirtualMousePointerDisplayId() {
+        if (com.android.input.flags.Flags.enablePointerChoreographer()) {
+            // We no longer need to get the pointer display when pointer choreographer is enabled.
+            return Display.INVALID_DISPLAY;
+        }
+        return mInputManagerInternal.getVirtualMousePointerDisplayId();
+    }
 }
diff --git a/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java b/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java
index b07d9a6..9c2e69b 100644
--- a/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java
+++ b/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java
@@ -520,7 +520,7 @@
         /**
          * Default value to {@link #mTrackerEnabled}.
          */
-        static final boolean DEFAULT_BG_BATTERY_EXEMPTION_ENABLED = true;
+        static final boolean DEFAULT_BG_BATTERY_EXEMPTION_ENABLED = false;
 
         AppBatteryExemptionPolicy(@NonNull Injector injector,
                 @NonNull AppBatteryExemptionTracker tracker) {
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index dc14c7a..7aafda5 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -153,6 +153,7 @@
         "machine_learning",
         "mainline_modularization",
         "mainline_sdk",
+        "make_pixel_haptics",
         "media_audio",
         "media_drm",
         "media_reliability",
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 99b45ec..cd295b5 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1047,11 +1047,9 @@
     private void initAudioHalBluetoothState() {
         synchronized (mBluetoothAudioStateLock) {
             mBluetoothScoOnApplied = false;
-            AudioSystem.setParameters("BT_SCO=off");
             mBluetoothA2dpSuspendedApplied = false;
-            AudioSystem.setParameters("A2dpSuspended=false");
             mBluetoothLeSuspendedApplied = false;
-            AudioSystem.setParameters("LeAudioSuspended=false");
+            reapplyAudioHalBluetoothState();
         }
     }
 
@@ -1114,6 +1112,34 @@
         }
     }
 
+    @GuardedBy("mBluetoothAudioStateLock")
+    private void reapplyAudioHalBluetoothState() {
+        if (AudioService.DEBUG_COMM_RTE) {
+            Log.v(TAG, "reapplyAudioHalBluetoothState() mBluetoothScoOnApplied: "
+                    + mBluetoothScoOnApplied + ", mBluetoothA2dpSuspendedApplied: "
+                    + mBluetoothA2dpSuspendedApplied + ", mBluetoothLeSuspendedApplied: "
+                    + mBluetoothLeSuspendedApplied);
+        }
+        // Note: the order of parameters is important.
+        if (mBluetoothScoOnApplied) {
+            AudioSystem.setParameters("A2dpSuspended=true");
+            AudioSystem.setParameters("LeAudioSuspended=true");
+            AudioSystem.setParameters("BT_SCO=on");
+        } else {
+            AudioSystem.setParameters("BT_SCO=off");
+            if (mBluetoothA2dpSuspendedApplied) {
+                AudioSystem.setParameters("A2dpSuspended=true");
+            } else {
+                AudioSystem.setParameters("A2dpSuspended=false");
+            }
+            if (mBluetoothLeSuspendedApplied) {
+                AudioSystem.setParameters("LeAudioSuspended=true");
+            } else {
+                AudioSystem.setParameters("LeAudioSuspended=false");
+            }
+        }
+    }
+
     /*package*/ void setBluetoothScoOn(boolean on, String eventSource) {
         if (AudioService.DEBUG_COMM_RTE) {
             Log.v(TAG, "setBluetoothScoOn: " + on + " " + eventSource);
@@ -1775,6 +1801,9 @@
                             initRoutingStrategyIds();
                             updateActiveCommunicationDevice();
                             mDeviceInventory.onRestoreDevices();
+                            synchronized (mBluetoothAudioStateLock) {
+                                reapplyAudioHalBluetoothState();
+                            }
                             mBtHelper.onAudioServerDiedRestoreA2dp();
                             updateCommunicationRoute("MSG_RESTORE_DEVICES");
                         }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 57b19cd..690c37a 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -914,28 +914,27 @@
                         di.mDeviceCodecFormat = codec;
                         mConnectedDevices.replace(key, di);
                         codecChange = true;
-                    }
-                    final int res = mAudioSystem.handleDeviceConfigChange(
-                            btInfo.mAudioSystemDevice, address, BtHelper.getName(btDevice), codec);
+                        final int res = mAudioSystem.handleDeviceConfigChange(
+                                btInfo.mAudioSystemDevice, address,
+                                BtHelper.getName(btDevice), codec);
+                        if (res != AudioSystem.AUDIO_STATUS_OK) {
+                            AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+                                    "APM handleDeviceConfigChange failed for A2DP device addr="
+                                            + address + " codec="
+                                            + AudioSystem.audioFormatToString(codec))
+                                    .printLog(TAG));
 
-                    if (res != AudioSystem.AUDIO_STATUS_OK) {
-                        AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
-                                "APM handleDeviceConfigChange failed for A2DP device addr="
-                                        + address + " codec="
-                                        + AudioSystem.audioFormatToString(codec))
-                                .printLog(TAG));
-
-                        // force A2DP device disconnection in case of error so that AudioService
-                        // state is consistent with audio policy manager state
-                        setBluetoothActiveDevice(new AudioDeviceBroker.BtDeviceInfo(btInfo,
-                                BluetoothProfile.STATE_DISCONNECTED));
-                    } else {
-                        AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
-                                "APM handleDeviceConfigChange success for A2DP device addr="
-                                        + address
-                                        + " codec=" + AudioSystem.audioFormatToString(codec))
-                                .printLog(TAG));
-
+                            // force A2DP device disconnection in case of error so that AudioService
+                            // state is consistent with audio policy manager state
+                            setBluetoothActiveDevice(new AudioDeviceBroker.BtDeviceInfo(btInfo,
+                                    BluetoothProfile.STATE_DISCONNECTED));
+                        } else {
+                            AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+                                    "APM handleDeviceConfigChange success for A2DP device addr="
+                                            + address
+                                            + " codec=" + AudioSystem.audioFormatToString(codec))
+                                    .printLog(TAG));
+                        }
                     }
                 }
                 if (!codecChange) {
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index a30cdc4..9610034ca 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -1203,7 +1203,7 @@
     @GuardedBy("mCsdStateLock")
     private void sanitizeDoseRecords_l() {
         if (mDoseRecords.size() > MAX_NUMBER_OF_CACHED_RECORDS) {
-            int nrToRemove = MAX_NUMBER_OF_CACHED_RECORDS - mDoseRecords.size();
+            int nrToRemove = mDoseRecords.size() - MAX_NUMBER_OF_CACHED_RECORDS;
             Log.w(TAG,
                     "Removing " + nrToRemove + " records from the total of " + mDoseRecords.size());
             // Remove older elements to fit into persisted settings max length
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 8fd2ee2..3f3540e 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.MANAGE_BIOMETRIC_DIALOG;
+import static android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO;
 import static android.Manifest.permission.TEST_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
@@ -305,7 +305,7 @@
             if (promptInfo.containsPrivateApiConfigurations()) {
                 checkInternalPermission();
             }
-            if (promptInfo.containsManageBioApiConfigurations()) {
+            if (promptInfo.containsSetLogoApiConfigurations()) {
                 checkManageBiometricPermission();
             }
 
@@ -439,6 +439,10 @@
             if (fingerprintService != null) {
                 fingerprintService.registerAuthenticationStateListener(listener);
             }
+            final IFaceService faceService = mInjector.getFaceService();
+            if (faceService != null) {
+                faceService.registerAuthenticationStateListener(listener);
+            }
         }
 
         @Override
@@ -449,6 +453,10 @@
             if (fingerprintService != null) {
                 fingerprintService.unregisterAuthenticationStateListener(listener);
             }
+            final IFaceService faceService = mInjector.getFaceService();
+            if (faceService != null) {
+                faceService.unregisterAuthenticationStateListener(listener);
+            }
         }
 
         @Override
@@ -989,8 +997,8 @@
     }
 
     private void checkManageBiometricPermission() {
-        getContext().enforceCallingOrSelfPermission(MANAGE_BIOMETRIC_DIALOG,
-                "Must have MANAGE_BIOMETRIC_DIALOG permission");
+        getContext().enforceCallingOrSelfPermission(SET_BIOMETRIC_DIALOG_LOGO,
+                "Must have SET_BIOMETRIC_DIALOG_LOGO permission");
     }
 
     private void checkPermission() {
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java
index 5863535..1ae4d64 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java
@@ -91,6 +91,40 @@
         }
     }
 
+    /**
+     * Defines behavior in response to a successful authentication
+     * @param requestReason Reason from [BiometricRequestConstants.RequestReason] for the requested
+     *                      authentication
+     * @param userId The user Id for the requested authentication
+     */
+    public void onAuthenticationSucceeded(int requestReason, int userId) {
+        for (AuthenticationStateListener listener: mAuthenticationStateListeners) {
+            try {
+                listener.onAuthenticationSucceeded(requestReason, userId);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception in notifying listener that authentication "
+                        + "succeeded", e);
+            }
+        }
+    }
+
+    /**
+     * Defines behavior in response to a failed authentication
+     * @param requestReason Reason from [BiometricRequestConstants.RequestReason] for the requested
+     *                      authentication
+     * @param userId The user Id for the requested authentication
+     */
+    public void onAuthenticationFailed(int requestReason, int userId) {
+        for (AuthenticationStateListener listener: mAuthenticationStateListeners) {
+            try {
+                listener.onAuthenticationFailed(requestReason, userId);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception in notifying listener that authentication "
+                        + "failed", e);
+            }
+        }
+    }
+
     @Override
     public void binderDied() {
         // Do nothing, handled below
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 73f3999..321e951 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -24,6 +24,7 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.Context;
+import android.hardware.biometrics.AuthenticationStateListener;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.IBiometricSensorReceiver;
 import android.hardware.biometrics.IBiometricService;
@@ -63,6 +64,7 @@
 import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -99,6 +101,8 @@
     private final BiometricStateCallback<ServiceProvider, FaceSensorPropertiesInternal>
             mBiometricStateCallback;
     @NonNull
+    private final AuthenticationStateListeners mAuthenticationStateListeners;
+    @NonNull
     private final FaceProviderFunction mFaceProviderFunction;
     @NonNull private final Function<String, FaceProvider> mFaceProvider;
     @NonNull
@@ -695,7 +699,8 @@
             for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
                 providers.add(
                         Face10.newInstance(getContext(), mBiometricStateCallback,
-                                hidlSensor, mLockoutResetDispatcher));
+                                mAuthenticationStateListeners, hidlSensor,
+                                mLockoutResetDispatcher));
             }
 
             return providers;
@@ -830,6 +835,24 @@
         public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
             mBiometricStateCallback.registerBiometricStateListener(listener);
         }
+
+        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+        @Override
+        public void registerAuthenticationStateListener(
+                @NonNull AuthenticationStateListener listener) {
+            super.registerAuthenticationStateListener_enforcePermission();
+
+            mAuthenticationStateListeners.registerAuthenticationStateListener(listener);
+        }
+
+        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+        @Override
+        public void unregisterAuthenticationStateListener(
+                @NonNull AuthenticationStateListener listener) {
+            super.unregisterAuthenticationStateListener_enforcePermission();
+
+            mAuthenticationStateListeners.unregisterAuthenticationStateListener(listener);
+        }
     }
 
     public FaceService(Context context) {
@@ -848,6 +871,7 @@
         mLockoutResetDispatcher = new LockoutResetDispatcher(context);
         mLockPatternUtils = new LockPatternUtils(context);
         mBiometricStateCallback = new BiometricStateCallback<>(UserManager.get(context));
+        mAuthenticationStateListeners = new AuthenticationStateListeners();
         mRegistry = new FaceServiceRegistry(mServiceWrapper, biometricServiceSupplier);
         mRegistry.addAllRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
             @Override
@@ -868,8 +892,8 @@
             try {
                 final SensorProps[] props = face.getSensorProps();
                 return new FaceProvider(getContext(),
-                        mBiometricStateCallback, props, name, mLockoutResetDispatcher,
-                        BiometricContext.getInstance(getContext()),
+                        mBiometricStateCallback, mAuthenticationStateListeners, props, name,
+                        mLockoutResetDispatcher, BiometricContext.getInstance(getContext()),
                         false /* resetLockoutRequiresChallenge */);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
@@ -881,7 +905,7 @@
         if (Flags.deHidl()) {
             mFaceProviderFunction = faceProviderFunction != null ? faceProviderFunction :
                     ((filteredSensorProps, resetLockoutRequiresChallenge) -> new FaceProvider(
-                            getContext(), mBiometricStateCallback,
+                            getContext(), mBiometricStateCallback, mAuthenticationStateListeners,
                             filteredSensorProps.second,
                             filteredSensorProps.first, mLockoutResetDispatcher,
                             BiometricContext.getInstance(getContext()),
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index 22e399c..f35de93 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors.face.aidl;
 
+import static android.adaptiveauth.Flags.reportBiometricAuthAttempts;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.NotificationManager;
@@ -44,6 +46,7 @@
 import com.android.server.biometrics.log.OperationContextExt;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
@@ -77,6 +80,8 @@
     private ICancellationSignal mCancellationSignal;
     @Nullable
     private final SensorPrivacyManager mSensorPrivacyManager;
+    @NonNull
+    private final AuthenticationStateListeners mAuthenticationStateListeners;
     @FaceManager.FaceAcquired
     private int mLastAcquire = FaceManager.FACE_ACQUIRED_UNKNOWN;
 
@@ -89,11 +94,13 @@
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             boolean isStrongBiometric, @NonNull UsageStats usageStats,
             @NonNull LockoutTracker lockoutCache, boolean allowBackgroundAuthentication,
-            @Authenticators.Types int sensorStrength) {
+            @Authenticators.Types int sensorStrength,
+            @NonNull AuthenticationStateListeners authenticationStateListeners) {
         this(context, lazyDaemon, token, requestId, listener, operationId,
                 restricted, options, cookie, requireConfirmation, logger, biometricContext,
                 isStrongBiometric, usageStats, lockoutCache, allowBackgroundAuthentication,
-                context.getSystemService(SensorPrivacyManager.class), sensorStrength);
+                context.getSystemService(SensorPrivacyManager.class), sensorStrength,
+                authenticationStateListeners);
     }
 
     @VisibleForTesting
@@ -107,7 +114,8 @@
             boolean isStrongBiometric, @NonNull UsageStats usageStats,
             @NonNull LockoutTracker lockoutTracker, boolean allowBackgroundAuthentication,
             SensorPrivacyManager sensorPrivacyManager,
-            @Authenticators.Types int biometricStrength) {
+            @Authenticators.Types int biometricStrength,
+            @NonNull AuthenticationStateListeners authenticationStateListeners) {
         super(context, lazyDaemon, token, listener, operationId, restricted,
                 options, cookie, requireConfirmation, logger, biometricContext,
                 isStrongBiometric, null /* taskStackListener */, lockoutTracker,
@@ -118,6 +126,7 @@
         mNotificationManager = context.getSystemService(NotificationManager.class);
         mSensorPrivacyManager = sensorPrivacyManager;
         mAuthSessionCoordinator = biometricContext.getAuthSessionCoordinator();
+        mAuthenticationStateListeners = authenticationStateListeners;
 
         final Resources resources = getContext().getResources();
         mBiometricPromptIgnoreList = resources.getIntArray(
@@ -262,6 +271,16 @@
                 0 /* error */,
                 0 /* vendorError */,
                 getTargetUserId()));
+
+        if (reportBiometricAuthAttempts()) {
+            if (authenticated) {
+                mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(),
+                        getTargetUserId());
+            } else {
+                mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(),
+                        getTargetUserId());
+            }
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index e4ecf1a..d01c268 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -59,6 +59,7 @@
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
@@ -103,6 +104,8 @@
     @NonNull
     private final BiometricStateCallback mBiometricStateCallback;
     @NonNull
+    private final AuthenticationStateListeners mAuthenticationStateListeners;
+    @NonNull
     private final String mHalInstanceName;
     @NonNull
     private final Handler mHandler;
@@ -156,18 +159,20 @@
 
     public FaceProvider(@NonNull Context context,
             @NonNull BiometricStateCallback biometricStateCallback,
+            @NonNull AuthenticationStateListeners authenticationStateListeners,
             @NonNull SensorProps[] props,
             @NonNull String halInstanceName,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull BiometricContext biometricContext,
             boolean resetLockoutRequiresChallenge) {
-        this(context, biometricStateCallback, props, halInstanceName, lockoutResetDispatcher,
-                biometricContext, null /* daemon */, getHandler(), resetLockoutRequiresChallenge,
-                false /* testHalEnabled */);
+        this(context, biometricStateCallback, authenticationStateListeners, props, halInstanceName,
+                lockoutResetDispatcher, biometricContext, null /* daemon */, getHandler(),
+                resetLockoutRequiresChallenge, false /* testHalEnabled */);
     }
 
     @VisibleForTesting FaceProvider(@NonNull Context context,
             @NonNull BiometricStateCallback biometricStateCallback,
+            @NonNull AuthenticationStateListeners authenticationStateListeners,
             @NonNull SensorProps[] props,
             @NonNull String halInstanceName,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
@@ -178,6 +183,7 @@
             boolean testHalEnabled) {
         mContext = context;
         mBiometricStateCallback = biometricStateCallback;
+        mAuthenticationStateListeners = authenticationStateListeners;
         mHalInstanceName = halInstanceName;
         mFaceSensors = new SensorList<>(ActivityManager.getService());
         if (Flags.deHidl()) {
@@ -610,7 +616,8 @@
                             mAuthenticationStatsCollector),
                     mBiometricContext, isStrongBiometric,
                     mUsageStats, lockoutTracker,
-                    allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId));
+                    allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId),
+                    mAuthenticationStateListeners);
             scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
                 @Override
                 public void onClientStarted(
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 5337666..48a676c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -64,6 +64,7 @@
 import com.android.server.biometrics.sensors.AcquisitionClient;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.AuthenticationConsumer;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
@@ -119,6 +120,8 @@
 
     @NonNull private final FaceSensorPropertiesInternal mSensorProperties;
     @NonNull private final BiometricStateCallback mBiometricStateCallback;
+    @NonNull
+    private final AuthenticationStateListeners mAuthenticationStateListeners;
     @NonNull private final Context mContext;
     @NonNull private final BiometricScheduler<IBiometricsFace, AidlSession> mScheduler;
     @NonNull private final Handler mHandler;
@@ -350,6 +353,7 @@
     @VisibleForTesting
     Face10(@NonNull Context context,
             @NonNull BiometricStateCallback biometricStateCallback,
+            @NonNull AuthenticationStateListeners authenticationStateListeners,
             @NonNull FaceSensorPropertiesInternal sensorProps,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull Handler handler,
@@ -358,6 +362,7 @@
         mSensorProperties = sensorProps;
         mContext = context;
         mBiometricStateCallback = biometricStateCallback;
+        mAuthenticationStateListeners = authenticationStateListeners;
         mSensorId = sensorProps.sensorId;
         mScheduler = scheduler;
         mHandler = handler;
@@ -392,11 +397,12 @@
 
     public static Face10 newInstance(@NonNull Context context,
             @NonNull BiometricStateCallback biometricStateCallback,
+            @NonNull AuthenticationStateListeners authenticationStateListeners,
             @NonNull FaceSensorPropertiesInternal sensorProps,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
         final Handler handler = new Handler(Looper.getMainLooper());
-        return new Face10(context, biometricStateCallback, sensorProps, lockoutResetDispatcher,
-                handler, new BiometricScheduler<>(
+        return new Face10(context, biometricStateCallback, authenticationStateListeners,
+                sensorProps, lockoutResetDispatcher, handler, new BiometricScheduler<>(
                         BiometricScheduler.SENSOR_TYPE_FACE,
                         null /* gestureAvailabilityTracker */),
                 BiometricContext.getInstance(context));
@@ -846,7 +852,8 @@
                         createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
                                 mAuthenticationStatsCollector), mBiometricContext,
                         isStrongBiometric, mUsageStats, mLockoutTracker,
-                        allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId));
+                        allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId),
+                        mAuthenticationStateListeners);
         mScheduler.scheduleClientMonitor(client);
     }
 
@@ -860,7 +867,8 @@
                 createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
                         mAuthenticationStatsCollector), mBiometricContext,
                 isStrongBiometric, mLockoutTracker, mUsageStats,
-                allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId));
+                allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId),
+                mAuthenticationStateListeners);
         mScheduler.scheduleClientMonitor(client);
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index 8ab8892..e44b263 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors.face.hidl;
 
+import static android.adaptiveauth.Flags.reportBiometricAuthAttempts;
+
 import android.annotation.NonNull;
 import android.content.Context;
 import android.content.res.Resources;
@@ -36,6 +38,7 @@
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.BiometricNotificationUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
@@ -65,6 +68,8 @@
 
     private int mLastAcquire;
     private SensorPrivacyManager mSensorPrivacyManager;
+    @NonNull
+    private final AuthenticationStateListeners mAuthenticationStateListeners;
 
     FaceAuthenticationClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFace> lazyDaemon,
@@ -75,7 +80,8 @@
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             boolean isStrongBiometric, @NonNull LockoutTracker lockoutTracker,
             @NonNull UsageStats usageStats, boolean allowBackgroundAuthentication,
-            @Authenticators.Types int sensorStrength) {
+            @Authenticators.Types int sensorStrength,
+            @NonNull AuthenticationStateListeners authenticationStateListeners) {
         super(context, lazyDaemon, token, listener, operationId, restricted,
                 options, cookie, requireConfirmation, logger, biometricContext,
                 isStrongBiometric, null /* taskStackListener */,
@@ -84,6 +90,7 @@
         setRequestId(requestId);
         mUsageStats = usageStats;
         mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
+        mAuthenticationStateListeners = authenticationStateListeners;
 
         final Resources resources = getContext().getResources();
         mBiometricPromptIgnoreList = resources.getIntArray(
@@ -186,6 +193,16 @@
                 0 /* error */,
                 0 /* vendorError */,
                 getTargetUserId()));
+
+        if (reportBiometricAuthAttempts()) {
+            if (authenticated) {
+                mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(),
+                        getTargetUserId());
+            } else {
+                mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(),
+                        getTargetUserId());
+            }
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index f7e8123..6912961 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors.fingerprint.aidl;
 
+import static android.adaptiveauth.Flags.reportBiometricAuthAttempts;
+
 import static com.android.systemui.shared.Flags.sidefpsControllerRefactor;
 
 import android.annotation.NonNull;
@@ -232,8 +234,16 @@
             if (sidefpsControllerRefactor()) {
                 mAuthenticationStateListeners.onAuthenticationStopped();
             }
+            if (reportBiometricAuthAttempts()) {
+                mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(),
+                        getTargetUserId());
+            }
         } else {
             mState = STATE_STARTED_PAUSED_ATTEMPTED;
+            if (reportBiometricAuthAttempts()) {
+                mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(),
+                        getTargetUserId());
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 4c1d4d6..7a329e9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors.fingerprint.hidl;
 
+import static android.adaptiveauth.Flags.reportBiometricAuthAttempts;
+
 import static com.android.systemui.shared.Flags.sidefpsControllerRefactor;
 
 import android.annotation.NonNull;
@@ -142,6 +144,10 @@
             if (sidefpsControllerRefactor()) {
                 mAuthenticationStateListeners.onAuthenticationStopped();
             }
+            if (reportBiometricAuthAttempts()) {
+                mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(),
+                        getTargetUserId());
+            }
         } else {
             mState = STATE_STARTED_PAUSED_ATTEMPTED;
             final @LockoutTracker.LockoutMode int lockoutMode =
@@ -161,6 +167,10 @@
                 onErrorInternal(errorCode, 0 /* vendorCode */, false /* finish */);
                 cancel();
             }
+            if (reportBiometricAuthAttempts()) {
+                mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(),
+                        getTargetUserId());
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index 380106b..b963a4b 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -87,12 +87,16 @@
      * connected, the caller may be blocked for an arbitrary period of time.
      *
      * @return true if the pointer displayId was set successfully, or false if it fails.
+     *
+     * @deprecated TODO(b/293587049): Not needed - remove after Pointer Icon Refactor is complete.
      */
     public abstract boolean setVirtualMousePointerDisplayId(int pointerDisplayId);
 
     /**
      * Gets the display id that the MouseCursorController is being forced to target. Returns
      * {@link android.view.Display#INVALID_DISPLAY} if there is no override
+     *
+     * @deprecated TODO(b/293587049): Not needed - remove after Pointer Icon Refactor is complete.
      */
     public abstract int getVirtualMousePointerDisplayId();
 
@@ -101,7 +105,7 @@
      *
      * Returns NaN-s as the coordinates if the cursor is not available.
      */
-    public abstract PointF getCursorPosition();
+    public abstract PointF getCursorPosition(int displayId);
 
     /**
      * Enables or disables pointer acceleration for mouse movements.
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 67c23fc..687def0 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -1448,6 +1448,10 @@
     }
 
     private boolean setVirtualMousePointerDisplayIdBlocking(int overrideDisplayId) {
+        if (com.android.input.flags.Flags.enablePointerChoreographer()) {
+            throw new IllegalStateException(
+                    "This must not be used when PointerChoreographer is enabled");
+        }
         final boolean isRemovingOverride = overrideDisplayId == Display.INVALID_DISPLAY;
 
         // Take care to not make calls to window manager while holding internal locks.
@@ -1486,6 +1490,10 @@
     }
 
     private int getVirtualMousePointerDisplayId() {
+        if (com.android.input.flags.Flags.enablePointerChoreographer()) {
+            throw new IllegalStateException(
+                    "This must not be used when PointerChoreographer is enabled");
+        }
         synchronized (mAdditionalDisplayInputPropertiesLock) {
             return mOverriddenPointerDisplayId;
         }
@@ -3332,8 +3340,8 @@
         }
 
         @Override
-        public PointF getCursorPosition() {
-            final float[] p = mNative.getMouseCursorPosition();
+        public PointF getCursorPosition(int displayId) {
+            final float[] p = mNative.getMouseCursorPosition(displayId);
             if (p == null || p.length != 2) {
                 throw new IllegalStateException("Failed to get mouse cursor position");
             }
@@ -3614,6 +3622,13 @@
     }
 
     /**
+     * Sets Accessibility slow keys threshold in milliseconds.
+     */
+    public void setAccessibilitySlowKeysThreshold(int thresholdTimeMs) {
+        mNative.setAccessibilitySlowKeysThreshold(thresholdTimeMs);
+    }
+
+    /**
      * Sets whether Accessibility sticky keys is enabled.
      */
     public void setAccessibilityStickyKeysEnabled(boolean enabled) {
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index 572d844..165dfe4 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -89,6 +89,8 @@
                         (reason) -> updateShowRotaryInput()),
                 Map.entry(Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BOUNCE_KEYS),
                         (reason) -> updateAccessibilityBounceKeys()),
+                Map.entry(Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_SLOW_KEYS),
+                        (reason) -> updateAccessibilitySlowKeys()),
                 Map.entry(Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_STICKY_KEYS),
                         (reason) -> updateAccessibilityStickyKeys()));
     }
@@ -228,6 +230,11 @@
                 InputSettings.getAccessibilityBounceKeysThreshold(mContext));
     }
 
+    private void updateAccessibilitySlowKeys() {
+        mService.setAccessibilitySlowKeysThreshold(
+                InputSettings.getAccessibilitySlowKeysThreshold(mContext));
+    }
+
     private void updateAccessibilityStickyKeys() {
         mService.setAccessibilityStickyKeysEnabled(
                 InputSettings.isAccessibilityStickyKeysEnabled(mContext));
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 8aec8ca..bc82078 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -233,14 +233,15 @@
     void setStylusButtonMotionEventsEnabled(boolean enabled);
 
     /**
-     * Get the current position of the mouse cursor.
+     * Get the current position of the mouse cursor on the given display.
      *
-     * If the mouse cursor is not currently shown, the coordinate values will be NaN-s.
+     * If the mouse cursor is not currently shown, the coordinate values will be NaN-s. Use
+     * {@link android.view.Display#INVALID_DISPLAY} to get the position of the default mouse cursor.
      *
      * NOTE: This will grab the PointerController's lock, so we must be careful about calling this
      * from the InputReader or Display threads, which may result in a deadlock.
      */
-    float[] getMouseCursorPosition();
+    float[] getMouseCursorPosition(int displayId);
 
     /** Set whether showing a pointer icon for styluses is enabled. */
     void setStylusPointerIconEnabled(boolean enabled);
@@ -257,6 +258,11 @@
     void setAccessibilityBounceKeysThreshold(int thresholdTimeMs);
 
     /**
+     * Notify if Accessibility slow keys threshold is changed from InputSettings.
+     */
+    void setAccessibilitySlowKeysThreshold(int thresholdTimeMs);
+
+    /**
      * Notify if Accessibility sticky keys is enabled/disabled from InputSettings.
      */
     void setAccessibilityStickyKeysEnabled(boolean enabled);
@@ -514,7 +520,7 @@
         public native void setStylusButtonMotionEventsEnabled(boolean enabled);
 
         @Override
-        public native float[] getMouseCursorPosition();
+        public native float[] getMouseCursorPosition(int displayId);
 
         @Override
         public native void setStylusPointerIconEnabled(boolean enabled);
@@ -526,6 +532,9 @@
         public native void setAccessibilityBounceKeysThreshold(int thresholdTimeMs);
 
         @Override
+        public native void setAccessibilitySlowKeysThreshold(int thresholdTimeMs);
+
+        @Override
         public native void setAccessibilityStickyKeysEnabled(boolean enabled);
     }
 }
diff --git a/services/core/java/com/android/server/inputmethod/ClientController.java b/services/core/java/com/android/server/inputmethod/ClientController.java
index 21b952b..ece236a 100644
--- a/services/core/java/com/android/server/inputmethod/ClientController.java
+++ b/services/core/java/com/android/server/inputmethod/ClientController.java
@@ -21,8 +21,6 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.ArrayMap;
-import android.util.SparseArray;
-import android.view.inputmethod.InputBinding;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -156,48 +154,4 @@
         return InputMethodUtils.checkIfPackageBelongsToUid(
                 mPackageManagerInternal, cs.mUid, packageName);
     }
-
-    static final class ClientState {
-        final IInputMethodClientInvoker mClient;
-        final IRemoteInputConnection mFallbackInputConnection;
-        final int mUid;
-        final int mPid;
-        final int mSelfReportedDisplayId;
-        final InputBinding mBinding;
-        final IBinder.DeathRecipient mClientDeathRecipient;
-
-        @GuardedBy("ImfLock.class")
-        boolean mSessionRequested;
-
-        @GuardedBy("ImfLock.class")
-        boolean mSessionRequestedForAccessibility;
-
-        @GuardedBy("ImfLock.class")
-        InputMethodManagerService.SessionState mCurSession;
-
-        @GuardedBy("ImfLock.class")
-        SparseArray<InputMethodManagerService.AccessibilitySessionState> mAccessibilitySessions =
-                new SparseArray<>();
-
-        @Override
-        public String toString() {
-            return "ClientState{" + Integer.toHexString(
-                    System.identityHashCode(this)) + " mUid=" + mUid
-                    + " mPid=" + mPid + " mSelfReportedDisplayId=" + mSelfReportedDisplayId + "}";
-        }
-
-        ClientState(IInputMethodClientInvoker client,
-                IRemoteInputConnection fallbackInputConnection,
-                int uid, int pid, int selfReportedDisplayId,
-                IBinder.DeathRecipient clientDeathRecipient) {
-            mClient = client;
-            mFallbackInputConnection = fallbackInputConnection;
-            mUid = uid;
-            mPid = pid;
-            mSelfReportedDisplayId = selfReportedDisplayId;
-            mBinding = new InputBinding(null /*conn*/, mFallbackInputConnection.asBinder(), mUid,
-                    mPid);
-            mClientDeathRecipient = clientDeathRecipient;
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/inputmethod/ClientState.java b/services/core/java/com/android/server/inputmethod/ClientState.java
new file mode 100644
index 0000000..e98a5a7
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/ClientState.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.os.IBinder;
+import android.util.SparseArray;
+import android.view.inputmethod.InputBinding;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.IRemoteInputConnection;
+
+final class ClientState {
+    final IInputMethodClientInvoker mClient;
+    final IRemoteInputConnection mFallbackInputConnection;
+    final int mUid;
+    final int mPid;
+    final int mSelfReportedDisplayId;
+    final InputBinding mBinding;
+    final IBinder.DeathRecipient mClientDeathRecipient;
+
+    @GuardedBy("ImfLock.class")
+    boolean mSessionRequested;
+
+    @GuardedBy("ImfLock.class")
+    boolean mSessionRequestedForAccessibility;
+
+    @GuardedBy("ImfLock.class")
+    InputMethodManagerService.SessionState mCurSession;
+
+    @GuardedBy("ImfLock.class")
+    SparseArray<InputMethodManagerService.AccessibilitySessionState> mAccessibilitySessions =
+            new SparseArray<>();
+
+    @Override
+    public String toString() {
+        return "ClientState{" + Integer.toHexString(
+                System.identityHashCode(this)) + " mUid=" + mUid
+                + " mPid=" + mPid + " mSelfReportedDisplayId=" + mSelfReportedDisplayId + "}";
+    }
+
+    ClientState(IInputMethodClientInvoker client,
+            IRemoteInputConnection fallbackInputConnection,
+            int uid, int pid, int selfReportedDisplayId,
+            IBinder.DeathRecipient clientDeathRecipient) {
+        mClient = client;
+        mFallbackInputConnection = fallbackInputConnection;
+        mUid = uid;
+        mPid = pid;
+        mSelfReportedDisplayId = selfReportedDisplayId;
+        mBinding = new InputBinding(null /*conn*/, mFallbackInputConnection.asBinder(), mUid,
+                mPid);
+        mClientDeathRecipient = clientDeathRecipient;
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 50340d2..5def428 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -48,7 +48,6 @@
 import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
 
-import static com.android.server.inputmethod.ClientController.ClientState;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeTargetWindowState;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeVisibilityResult;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
@@ -116,7 +115,6 @@
 import android.util.Printer;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.SparseBooleanArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.InputChannel;
 import android.view.InputDevice;
@@ -282,8 +280,6 @@
     @NonNull
     private InputMethodSettings mSettings;
     final SettingsObserver mSettingsObserver;
-    private final SparseBooleanArray mLoggedDeniedGetInputMethodWindowVisibleHeightForUid =
-            new SparseBooleanArray(0);
     final WindowManagerInternal mWindowManagerInternal;
     private final ActivityManagerInternal mActivityManagerInternal;
     final PackageManagerInternal mPackageManagerInternal;
@@ -1355,13 +1351,6 @@
             clearPackageChangeState();
         }
 
-        @Override
-        public void onUidRemoved(int uid) {
-            synchronized (ImfLock.class) {
-                mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.delete(uid);
-            }
-        }
-
         private void clearPackageChangeState() {
             // No need to lock them because we access these fields only on getRegisteredHandler().
             mChangedPackages.clear();
@@ -2176,7 +2165,7 @@
     /**
      * Hide the IME if the removed user is the current user.
      */
-    private void onClientRemoved(ClientController.ClientState client) {
+    private void onClientRemoved(ClientState client) {
         synchronized (ImfLock.class) {
             clearClientSessionLocked(client);
             clearClientSessionForAccessibilityLocked(client);
@@ -4277,10 +4266,6 @@
             synchronized (ImfLock.class) {
                 if (!canInteractWithImeLocked(callingUid, client,
                         "getInputMethodWindowVisibleHeight", null /* statsToken */)) {
-                    if (!mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.get(callingUid)) {
-                        EventLog.writeEvent(0x534e4554, "204906124", callingUid, "");
-                        mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.put(callingUid, true);
-                    }
                     return 0;
                 }
                 // This should probably use the caller's display id, but because this is unsupported
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 2cd3ab1..1d516e2 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -287,10 +287,14 @@
                 }
                 user.mPriorityStack.onSessionActiveStateChanged(record);
             }
-            setForegroundServiceAllowance(
-                    record,
-                    /* allowRunningInForeground= */ record.isActive()
-                            && (playbackState == null || playbackState.isActive()));
+            boolean allowRunningInForeground = record.isActive()
+                    && (playbackState == null || playbackState.isActive());
+
+            Log.d(TAG, "onSessionActiveStateChanged: "
+                    + "record=" + record
+                    + "playbackState=" + playbackState
+                    + "allowRunningInForeground=" + allowRunningInForeground);
+            setForegroundServiceAllowance(record, allowRunningInForeground);
             mHandler.postSessionsChanged(record);
         }
     }
@@ -388,10 +392,12 @@
             }
             user.mPriorityStack.onPlaybackStateChanged(record, shouldUpdatePriority);
             if (playbackState != null) {
-                setForegroundServiceAllowance(
-                        record,
-                        /* allowRunningInForeground= */ playbackState.isActive()
-                                && record.isActive());
+                boolean allowRunningInForeground = playbackState.isActive() && record.isActive();
+                Log.d(TAG, "onSessionPlaybackStateChanged: "
+                        + "record=" + record
+                        + "playbackState=" + playbackState
+                        + "allowRunningInForeground=" + allowRunningInForeground);
+                setForegroundServiceAllowance(record, allowRunningInForeground);
             }
         }
     }
@@ -556,6 +562,8 @@
         }
 
         session.close();
+
+        Log.d(TAG, "destroySessionLocked: record=" + session);
         setForegroundServiceAllowance(session, /* allowRunningInForeground= */ false);
         mHandler.postSessionsChanged(session);
     }
diff --git a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
index df612e6..bbe6d3a 100644
--- a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
+++ b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
@@ -20,6 +20,7 @@
 import android.content.pm.PackageManager;
 import android.media.MediaMetrics;
 import android.media.metrics.BundleSession;
+import android.media.metrics.EditingEndedEvent;
 import android.media.metrics.IMediaMetricsManager;
 import android.media.metrics.NetworkEvent;
 import android.media.metrics.PlaybackErrorEvent;
@@ -346,6 +347,24 @@
             StatsLog.write(statsEvent);
         }
 
+        @Override
+        public void reportEditingEndedEvent(String sessionId, EditingEndedEvent event, int userId) {
+            int level = loggingLevel();
+            if (level == LOGGING_LEVEL_BLOCKED) {
+                return;
+            }
+            StatsEvent statsEvent =
+                    StatsEvent.newBuilder()
+                            .setAtomId(798)
+                            .writeString(sessionId)
+                            .writeInt(event.getFinalState())
+                            .writeInt(event.getErrorCode())
+                            .writeLong(event.getTimeSinceCreatedMillis())
+                            .usePooledBuffer()
+                            .build();
+            StatsLog.write(statsEvent);
+        }
+
         private int loggingLevel() {
             synchronized (mLock) {
                 int uid = Binder.getCallingUid();
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index c067fa0..923be56d 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -1183,7 +1183,7 @@
                     != newPolicy.getPriorityConversationSenders()) {
                 userModifiedFields |= ZenPolicy.FIELD_CONVERSATIONS;
             }
-            if (oldPolicy.getPriorityChannels() != newPolicy.getPriorityChannels()) {
+            if (oldPolicy.getPriorityChannelsAllowed() != newPolicy.getPriorityChannelsAllowed()) {
                 userModifiedFields |= ZenPolicy.FIELD_ALLOW_CHANNELS;
             }
             if (oldPolicy.getPriorityCategoryReminders()
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 1660c3e..8452c0e 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -152,14 +152,13 @@
         @RequiresPermission(value = android.Manifest.permission.INTERACT_ACROSS_USERS,
                 conditional = true)
         void ensureCallerPreviouslyGeneratedFile(
-                Context context, Pair<Integer, String> callingInfo, int userId,
-                String bugreportFile, boolean forceUpdateMapping) {
+                Context context, PackageManager packageManager, Pair<Integer, String> callingInfo,
+                int userId, String bugreportFile, boolean forceUpdateMapping) {
             synchronized (mLock) {
                 if (onboardingBugreportV2Enabled()) {
                     final int uidForUser = Binder.withCleanCallingIdentity(() -> {
                         try {
-                            return context.getPackageManager()
-                                    .getPackageUidAsUser(callingInfo.second, userId);
+                            return packageManager.getPackageUidAsUser(callingInfo.second, userId);
                         } catch (PackageManager.NameNotFoundException exception) {
                             throwInvalidBugreportFileForCallerException(
                                     bugreportFile, callingInfo.second);
@@ -441,8 +440,8 @@
         Slogf.i(TAG, "Retrieving bugreport for %s / %d", callingPackage, callingUid);
         try {
             mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
-                    mContext, new Pair<>(callingUid, callingPackage), userId, bugreportFile,
-                    /* forceUpdateMapping= */ false);
+                    mContext, mContext.getPackageManager(), new Pair<>(callingUid, callingPackage),
+                    userId, bugreportFile, /* forceUpdateMapping= */ false);
         } catch (IllegalArgumentException e) {
             Slog.e(TAG, e.getMessage());
             reportError(listener, IDumpstateListener.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE);
diff --git a/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS b/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS
new file mode 100644
index 0000000..baa41a5
--- /dev/null
+++ b/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS
@@ -0,0 +1,2 @@
+georgechan@google.com
+wenhaowang@google.com
\ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index f311034..ada79ae 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -4217,8 +4217,10 @@
             }
         }
 
+        final long firstInstallTime = Flags.fixSystemAppsFirstInstallTime()
+                ? System.currentTimeMillis() : 0;
         final ScanResult scanResult = scanPackageNewLI(parsedPackage, parseFlags,
-                scanFlags | SCAN_UPDATE_SIGNATURE, 0 /* currentTime */, user, null);
+                scanFlags | SCAN_UPDATE_SIGNATURE, firstInstallTime, user, null);
         return new Pair<>(scanResult, shouldHideSystemApp);
     }
 
diff --git a/services/core/java/com/android/server/pm/OWNERS b/services/core/java/com/android/server/pm/OWNERS
index 84324f2..c8bc56c 100644
--- a/services/core/java/com/android/server/pm/OWNERS
+++ b/services/core/java/com/android/server/pm/OWNERS
@@ -51,3 +51,5 @@
 per-file ShortcutService.java = omakoto@google.com, yamasani@google.com, sunnygoyal@google.com, mett@google.com, pinyaoting@google.com
 per-file ShortcutUser.java = omakoto@google.com, yamasani@google.com, sunnygoyal@google.com, mett@google.com, pinyaoting@google.com
 
+# background install control service
+per-file BackgroundInstall* = file:BACKGROUND_INSTALL_OWNERS
\ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/PreferredComponent.java b/services/core/java/com/android/server/pm/PreferredComponent.java
index 18caafd..f3b1464 100644
--- a/services/core/java/com/android/server/pm/PreferredComponent.java
+++ b/services/core/java/com/android/server/pm/PreferredComponent.java
@@ -28,7 +28,8 @@
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.LocalServices;
-import com.android.server.pm.pkg.PackageUserState;
+import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.PackageUserStateInternal;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -218,11 +219,15 @@
                 continue;
             }
 
-            // Avoid showing the disambiguation dialog if the package which is installed with
-            // reason INSTALL_REASON_DEVICE_SETUP.
-            final PackageUserState pkgUserState =
-                    pmi.getPackageStateInternal(ai.packageName).getUserStates().get(userId);
-            if (pkgUserState != null && pkgUserState.getInstallReason()
+            // Avoid showing the disambiguation dialog if the package is not installed or
+            // installed with reason INSTALL_REASON_DEVICE_SETUP.
+            final PackageStateInternal ps = pmi.getPackageStateInternal(ai.packageName);
+            if (ps == null) {
+                continue;
+            }
+            final PackageUserStateInternal pkgUserState = ps.getUserStates().get(userId);
+            if (pkgUserState == null
+                    || pkgUserState.getInstallReason()
                     == PackageManager.INSTALL_REASON_DEVICE_SETUP) {
                 continue;
             }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index a6598d6..c0596bb 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -295,8 +295,6 @@
 
     private static final int USER_VERSION = 11;
 
-    private static final int MAX_USER_STRING_LENGTH = 500;
-
     private static final long EPOCH_PLUS_30_YEARS = 30L * 365 * 24 * 60 * 60 * 1000L; // ms
 
     static final int WRITE_USER_MSG = 1;
@@ -4692,16 +4690,18 @@
         if (userData.persistSeedData) {
             if (userData.seedAccountName != null) {
                 serializer.attribute(null, ATTR_SEED_ACCOUNT_NAME,
-                        truncateString(userData.seedAccountName));
+                        truncateString(userData.seedAccountName,
+                                UserManager.MAX_ACCOUNT_STRING_LENGTH));
             }
             if (userData.seedAccountType != null) {
                 serializer.attribute(null, ATTR_SEED_ACCOUNT_TYPE,
-                        truncateString(userData.seedAccountType));
+                        truncateString(userData.seedAccountType,
+                                UserManager.MAX_ACCOUNT_STRING_LENGTH));
             }
         }
         if (userInfo.name != null) {
             serializer.startTag(null, TAG_NAME);
-            serializer.text(truncateString(userInfo.name));
+            serializer.text(truncateString(userInfo.name, UserManager.MAX_USER_NAME_LENGTH));
             serializer.endTag(null, TAG_NAME);
         }
         synchronized (mRestrictionsLock) {
@@ -4765,11 +4765,11 @@
         serializer.endDocument();
     }
 
-    private String truncateString(String original) {
-        if (original == null || original.length() <= MAX_USER_STRING_LENGTH) {
+    private String truncateString(String original, int limit) {
+        if (original == null || original.length() <= limit) {
             return original;
         }
-        return original.substring(0, MAX_USER_STRING_LENGTH);
+        return original.substring(0, limit);
     }
 
     /*
@@ -5236,7 +5236,7 @@
             @UserIdInt int parentId, boolean preCreate, @Nullable String[] disallowedPackages,
             @NonNull TimingsTraceAndSlog t, @Nullable Object token)
             throws UserManager.CheckedUserOperationException {
-        String truncatedName = truncateString(name);
+        String truncatedName = truncateString(name, UserManager.MAX_USER_NAME_LENGTH);
         final UserTypeDetails userTypeDetails = mUserTypes.get(userType);
         if (userTypeDetails == null) {
             throwCheckedUserOperationException(
@@ -6821,9 +6821,14 @@
                     Slog.e(LOG_TAG, "No such user for settings seed data u=" + userId);
                     return;
                 }
-                userData.seedAccountName = truncateString(accountName);
-                userData.seedAccountType = truncateString(accountType);
-                userData.seedAccountOptions = accountOptions;
+                userData.seedAccountName = truncateString(accountName,
+                        UserManager.MAX_ACCOUNT_STRING_LENGTH);
+                userData.seedAccountType = truncateString(accountType,
+                        UserManager.MAX_ACCOUNT_STRING_LENGTH);
+                if (accountOptions != null && accountOptions.isBundleContentsWithinLengthLimit(
+                        UserManager.MAX_ACCOUNT_OPTIONS_LENGTH)) {
+                    userData.seedAccountOptions = accountOptions;
+                }
                 userData.persistSeedData = persist;
             }
             if (persist) {
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index f0ff85d..dd2b409 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -357,7 +357,8 @@
             verifierUser = UserHandle.of(mPm.mUserManager.getCurrentUserId());
         }
         // TODO(b/300965895): Remove when inconsistencies loading classpaths from apex for
-        // user > 1 are fixed.
+        // user > 1 are fixed. Tests should cover verifiers from apex classpaths run on
+        // primary user, secondary user and work profile.
         if (pkgLite.isSdkLibrary) {
             verifierUser = UserHandle.SYSTEM;
         }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 0abf304..1fdcc64 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2185,6 +2185,10 @@
         TalkbackShortcutController getTalkbackShortcutController() {
             return new TalkbackShortcutController(mContext);
         }
+
+        WindowWakeUpPolicy getWindowWakeUpPolicy() {
+            return new WindowWakeUpPolicy(mContext);
+        }
     }
 
     /** {@inheritDoc} */
@@ -2433,7 +2437,7 @@
                 com.android.internal.R.integer.config_keyguardDrawnTimeout);
         mKeyguardDelegate = injector.getKeyguardServiceDelegate();
         mTalkbackShortcutController = injector.getTalkbackShortcutController();
-        mWindowWakeUpPolicy = new WindowWakeUpPolicy(mContext);
+        mWindowWakeUpPolicy = injector.getWindowWakeUpPolicy();
         initKeyCombinationRules();
         initSingleKeyGestureRules(injector.getLooper());
         mButtonOverridePermissionChecker = injector.getButtonOverridePermissionChecker();
diff --git a/services/core/java/com/android/server/selinux/QuotaLimiter.java b/services/core/java/com/android/server/selinux/QuotaLimiter.java
new file mode 100644
index 0000000..e89ddfd
--- /dev/null
+++ b/services/core/java/com/android/server/selinux/QuotaLimiter.java
@@ -0,0 +1,78 @@
+/*
+ * 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.selinux;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.Clock;
+
+import java.time.Duration;
+import java.time.Instant;
+
+/**
+ * A QuotaLimiter allows to define a maximum number of Atom pushes within a specific time window.
+ *
+ * <p>The limiter divides the time line in windows of a fixed size. Every time a new permit is
+ * requested, the limiter checks whether the previous request was in the same time window as the
+ * current one. If the two windows are the same, it grants a permit only if the number of permits
+ * granted within the window does not exceed the quota. If the two windows are different, it resets
+ * the quota.
+ */
+public class QuotaLimiter {
+
+    private final Clock mClock;
+    private final Duration mWindowSize;
+    private final int mMaxPermits;
+
+    private long mCurrentWindow = 0;
+    private int mPermitsGranted = 0;
+
+    @VisibleForTesting
+    QuotaLimiter(Clock clock, Duration windowSize, int maxPermits) {
+        mClock = clock;
+        mWindowSize = windowSize;
+        mMaxPermits = maxPermits;
+    }
+
+    public QuotaLimiter(Duration windowSize, int maxPermits) {
+        this(Clock.SYSTEM_CLOCK, windowSize, maxPermits);
+    }
+
+    public QuotaLimiter(int maxPermitsPerDay) {
+        this(Clock.SYSTEM_CLOCK, Duration.ofDays(1), maxPermitsPerDay);
+    }
+
+    /**
+     * Acquires a permit if there is one available in the current time window.
+     *
+     * @return true if a permit was acquired.
+     */
+    boolean acquire() {
+        long nowWindow =
+                Duration.between(Instant.EPOCH, Instant.ofEpochMilli(mClock.currentTimeMillis()))
+                        .dividedBy(mWindowSize);
+        if (nowWindow > mCurrentWindow) {
+            mCurrentWindow = nowWindow;
+            mPermitsGranted = 0;
+        }
+
+        if (mPermitsGranted < mMaxPermits) {
+            mPermitsGranted++;
+            return true;
+        }
+
+        return false;
+    }
+}
diff --git a/services/core/java/com/android/server/selinux/RateLimiter.java b/services/core/java/com/android/server/selinux/RateLimiter.java
new file mode 100644
index 0000000..599b840
--- /dev/null
+++ b/services/core/java/com/android/server/selinux/RateLimiter.java
@@ -0,0 +1,85 @@
+/*
+ * 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.selinux;
+
+import android.os.SystemClock;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.Clock;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+
+/**
+ * Rate limiter to ensure Atoms are pushed only within the allowed QPS window. This class is not
+ * thread-safe.
+ *
+ * <p>The rate limiter is smoothed, meaning that a rate limiter allowing X permits per second (or X
+ * QPS) will grant permits at a ratio of one every 1/X seconds.
+ */
+public final class RateLimiter {
+
+    private Instant mNextPermit = Instant.EPOCH;
+
+    private final Clock mClock;
+    private final Duration mWindow;
+
+    @VisibleForTesting
+    RateLimiter(Clock clock, Duration window) {
+        mClock = clock;
+        // Truncating because the system clock does not support units smaller than milliseconds.
+        mWindow = window;
+    }
+
+    /**
+     * Create a rate limiter generating one permit every {@code window} of time, using the {@link
+     * Clock.SYSTEM_CLOCK}.
+     */
+    public RateLimiter(Duration window) {
+        this(Clock.SYSTEM_CLOCK, window);
+    }
+
+    /**
+     * Acquire a permit if allowed by the rate limiter. If not, wait until a permit becomes
+     * available.
+     */
+    public void acquire() {
+        Instant now = Instant.ofEpochMilli(mClock.currentTimeMillis());
+
+        if (mNextPermit.isAfter(now)) { // Sleep until we can acquire.
+            SystemClock.sleep(ChronoUnit.MILLIS.between(now, mNextPermit));
+            mNextPermit = mNextPermit.plus(mWindow);
+        } else {
+            mNextPermit = now.plus(mWindow);
+        }
+    }
+
+    /**
+     * Try to acquire a permit if allowed by the rate limiter. Non-blocking.
+     *
+     * @return true if a permit was acquired. Otherwise, return false.
+     */
+    public boolean tryAcquire() {
+        final Instant now = Instant.ofEpochMilli(mClock.currentTimeMillis());
+
+        if (mNextPermit.isAfter(now)) {
+            return false;
+        }
+        mNextPermit = now.plus(mWindow);
+        return true;
+    }
+}
diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java
new file mode 100644
index 0000000..8d8d596
--- /dev/null
+++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java
@@ -0,0 +1,155 @@
+/*
+ * 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.selinux;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+
+/** Builder for SelinuxAuditLogs. */
+class SelinuxAuditLogBuilder {
+
+    // Currently logs collection is hardcoded for the sdk_sandbox_audit.
+    private static final String SDK_SANDBOX_AUDIT = "sdk_sandbox_audit";
+    static final Matcher SCONTEXT_MATCHER =
+            Pattern.compile(
+                            "u:r:(?<stype>"
+                                    + SDK_SANDBOX_AUDIT
+                                    + "):s0(:c)?(?<scategories>((,c)?\\d+)+)*")
+                    .matcher("");
+
+    static final Matcher TCONTEXT_MATCHER =
+            Pattern.compile("u:object_r:(?<ttype>\\w+):s0(:c)?(?<tcategories>((,c)?\\d+)+)*")
+                    .matcher("");
+
+    static final Matcher PATH_MATCHER =
+            Pattern.compile("\"(?<path>/\\w+(/\\w+)?)(/\\w+)*\"").matcher("");
+
+    private Iterator<String> mTokens;
+    private final SelinuxAuditLog mAuditLog = new SelinuxAuditLog();
+
+    void reset(String denialString) {
+        mTokens =
+                Arrays.asList(
+                                Optional.ofNullable(denialString)
+                                        .map(s -> s.split("\\s+|="))
+                                        .orElse(new String[0]))
+                        .iterator();
+        mAuditLog.reset();
+    }
+
+    SelinuxAuditLog build() {
+        while (mTokens.hasNext()) {
+            final String token = mTokens.next();
+
+            switch (token) {
+                case "granted":
+                    mAuditLog.mGranted = true;
+                    break;
+                case "denied":
+                    mAuditLog.mGranted = false;
+                    break;
+                case "{":
+                    Stream.Builder<String> permissionsStream = Stream.builder();
+                    boolean closed = false;
+                    while (!closed && mTokens.hasNext()) {
+                        String permission = mTokens.next();
+                        if ("}".equals(permission)) {
+                            closed = true;
+                        } else {
+                            permissionsStream.add(permission);
+                        }
+                    }
+                    if (!closed) {
+                        return null;
+                    }
+                    mAuditLog.mPermissions = permissionsStream.build().toArray(String[]::new);
+                    break;
+                case "scontext":
+                    if (!nextTokenMatches(SCONTEXT_MATCHER)) {
+                        return null;
+                    }
+                    mAuditLog.mSType = SCONTEXT_MATCHER.group("stype");
+                    mAuditLog.mSCategories = toCategories(SCONTEXT_MATCHER.group("scategories"));
+                    break;
+                case "tcontext":
+                    if (!nextTokenMatches(TCONTEXT_MATCHER)) {
+                        return null;
+                    }
+                    mAuditLog.mTType = TCONTEXT_MATCHER.group("ttype");
+                    mAuditLog.mTCategories = toCategories(TCONTEXT_MATCHER.group("tcategories"));
+                    break;
+                case "tclass":
+                    if (!mTokens.hasNext()) {
+                        return null;
+                    }
+                    mAuditLog.mTClass = mTokens.next();
+                    break;
+                case "path":
+                    if (nextTokenMatches(PATH_MATCHER)) {
+                        mAuditLog.mPath = PATH_MATCHER.group("path");
+                    }
+                    break;
+                case "permissive":
+                    if (!mTokens.hasNext()) {
+                        return null;
+                    }
+                    mAuditLog.mPermissive = "1".equals(mTokens.next());
+                    break;
+                default:
+                    break;
+            }
+        }
+        return mAuditLog;
+    }
+
+    boolean nextTokenMatches(Matcher matcher) {
+        return mTokens.hasNext() && matcher.reset(mTokens.next()).matches();
+    }
+
+    static int[] toCategories(String categories) {
+        return categories == null
+                ? null
+                : Arrays.stream(categories.split(",c")).mapToInt(Integer::parseInt).toArray();
+    }
+
+    static class SelinuxAuditLog {
+        boolean mGranted = false;
+        String[] mPermissions = null;
+        String mSType = null;
+        int[] mSCategories = null;
+        String mTType = null;
+        int[] mTCategories = null;
+        String mTClass = null;
+        String mPath = null;
+        boolean mPermissive = false;
+
+        private void reset() {
+            mGranted = false;
+            mPermissions = null;
+            mSType = null;
+            mSCategories = null;
+            mTType = null;
+            mTCategories = null;
+            mTClass = null;
+            mPath = null;
+            mPermissive = false;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java
new file mode 100644
index 0000000..0219645
--- /dev/null
+++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java
@@ -0,0 +1,144 @@
+/*
+ * 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.selinux;
+
+import android.util.EventLog;
+import android.util.EventLog.Event;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.selinux.SelinuxAuditLogBuilder.SelinuxAuditLog;
+
+import java.io.IOException;
+import java.time.Instant;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/** Class in charge of collecting SELinux audit logs and push the SELinux atoms. */
+class SelinuxAuditLogsCollector {
+
+    private static final String TAG = "SelinuxAuditLogs";
+
+    private static final String SELINUX_PATTERN = "^.*\\bavc:\\s+(?<denial>.*)$";
+
+    @VisibleForTesting
+    static final Matcher SELINUX_MATCHER = Pattern.compile(SELINUX_PATTERN).matcher("");
+
+    private final RateLimiter mRateLimiter;
+    private final QuotaLimiter mQuotaLimiter;
+
+    @VisibleForTesting Instant mLastWrite = Instant.MIN;
+
+    final AtomicBoolean mStopRequested = new AtomicBoolean(false);
+
+    SelinuxAuditLogsCollector(RateLimiter rateLimiter, QuotaLimiter quotaLimiter) {
+        mRateLimiter = rateLimiter;
+        mQuotaLimiter = quotaLimiter;
+    }
+
+    /**
+     * Collect and push SELinux audit logs for the provided {@code tagCode}.
+     *
+     * @return true if the job was completed. If the job was interrupted, return false.
+     */
+    boolean collect(int tagCode) {
+        Queue<Event> logLines = new ArrayDeque<>();
+        Instant latestTimestamp = collectLogLines(tagCode, logLines);
+
+        boolean quotaExceeded = writeAuditLogs(logLines);
+        if (quotaExceeded) {
+            Log.w(TAG, "Too many SELinux logs in the queue, I am giving up.");
+            mLastWrite = latestTimestamp; // next run we will ignore all these logs.
+            logLines.clear();
+        }
+
+        return logLines.isEmpty();
+    }
+
+    private Instant collectLogLines(int tagCode, Queue<Event> logLines) {
+        List<Event> events = new ArrayList<>();
+        try {
+            EventLog.readEvents(new int[] {tagCode}, events);
+        } catch (IOException e) {
+            Log.e(TAG, "Error reading event logs", e);
+        }
+
+        Instant latestTimestamp = mLastWrite;
+        for (Event event : events) {
+            Instant eventTime = Instant.ofEpochSecond(0, event.getTimeNanos());
+            if (eventTime.isAfter(latestTimestamp)) {
+                latestTimestamp = eventTime;
+            }
+            if (eventTime.isBefore(mLastWrite)) {
+                continue;
+            }
+            Object eventData = event.getData();
+            if (!(eventData instanceof String)) {
+                continue;
+            }
+            logLines.add(event);
+        }
+        return latestTimestamp;
+    }
+
+    private boolean writeAuditLogs(Queue<Event> logLines) {
+        final SelinuxAuditLogBuilder auditLogBuilder = new SelinuxAuditLogBuilder();
+
+        while (!mStopRequested.get() && !logLines.isEmpty()) {
+            Event event = logLines.poll();
+            String logLine = (String) event.getData();
+            Instant logTime = Instant.ofEpochSecond(0, event.getTimeNanos());
+            if (!SELINUX_MATCHER.reset(logLine).matches()) {
+                continue;
+            }
+
+            auditLogBuilder.reset(SELINUX_MATCHER.group("denial"));
+            final SelinuxAuditLog auditLog = auditLogBuilder.build();
+            if (auditLog == null) {
+                continue;
+            }
+
+            if (!mQuotaLimiter.acquire()) {
+                return true;
+            }
+            mRateLimiter.acquire();
+
+            FrameworkStatsLog.write(
+                    FrameworkStatsLog.SELINUX_AUDIT_LOG,
+                    auditLog.mGranted,
+                    auditLog.mPermissions,
+                    auditLog.mSType,
+                    auditLog.mSCategories,
+                    auditLog.mTType,
+                    auditLog.mTCategories,
+                    auditLog.mTClass,
+                    auditLog.mPath,
+                    auditLog.mPermissive);
+
+            if (logTime.isAfter(mLastWrite)) {
+                mLastWrite = logTime;
+            }
+        }
+
+        return false;
+    }
+}
diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java
new file mode 100644
index 0000000..8a661bc
--- /dev/null
+++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java
@@ -0,0 +1,132 @@
+/*
+ * 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.selinux;
+
+import static com.android.sdksandbox.flags.Flags.selinuxSdkSandboxAudit;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.util.EventLog;
+import android.util.Log;
+
+import java.time.Duration;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Scheduled jobs related to logging of SELinux denials and audits. The job runs daily on idle
+ * devices.
+ */
+public class SelinuxAuditLogsService extends JobService {
+
+    private static final String TAG = "SelinuxAuditLogs";
+    private static final String SELINUX_AUDIT_NAMESPACE = "SelinuxAuditLogsNamespace";
+
+    static final int AUDITD_TAG_CODE = EventLog.getTagCode("auditd");
+
+    private static final int SELINUX_AUDIT_JOB_ID = 25327386;
+    private static final JobInfo SELINUX_AUDIT_JOB =
+            new JobInfo.Builder(
+                            SELINUX_AUDIT_JOB_ID,
+                            new ComponentName("android", SelinuxAuditLogsService.class.getName()))
+                    .setPeriodic(TimeUnit.DAYS.toMillis(1))
+                    .setRequiresDeviceIdle(true)
+                    .setRequiresCharging(true)
+                    .setRequiresBatteryNotLow(true)
+                    .build();
+
+    private static final ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor();
+    private static final AtomicReference<Boolean> IS_RUNNING = new AtomicReference<>(false);
+
+    // Audit logging is subject to both rate and quota limiting. We can only push one atom every 10
+    // milliseconds, and no more than 50K atoms can be pushed each day.
+    private static final SelinuxAuditLogsCollector AUDIT_LOGS_COLLECTOR =
+            new SelinuxAuditLogsCollector(
+                    new RateLimiter(/* window= */ Duration.ofMillis(10)),
+                    new QuotaLimiter(/* maxPermitsPerDay= */ 50000));
+
+    /** Schedule jobs with the {@link JobScheduler}. */
+    public static void schedule(Context context) {
+        if (!selinuxSdkSandboxAudit()) {
+            Log.d(TAG, "SelinuxAuditLogsService not enabled");
+            return;
+        }
+
+        if (AUDITD_TAG_CODE == -1) {
+            Log.e(TAG, "auditd is not a registered tag on this system");
+            return;
+        }
+
+        if (context.getSystemService(JobScheduler.class)
+                        .forNamespace(SELINUX_AUDIT_NAMESPACE)
+                        .schedule(SELINUX_AUDIT_JOB)
+                == JobScheduler.RESULT_FAILURE) {
+            Log.e(TAG, "SelinuxAuditLogsService could not be started.");
+        }
+    }
+
+    @Override
+    public boolean onStartJob(JobParameters params) {
+        if (params.getJobId() != SELINUX_AUDIT_JOB_ID) {
+            Log.e(TAG, "The job id does not match the expected selinux job id.");
+            return false;
+        }
+
+        AUDIT_LOGS_COLLECTOR.mStopRequested.set(false);
+        IS_RUNNING.set(true);
+        EXECUTOR_SERVICE.execute(new LogsCollectorJob(this, params));
+
+        return true; // the job is running
+    }
+
+    @Override
+    public boolean onStopJob(JobParameters params) {
+        if (params.getJobId() != SELINUX_AUDIT_JOB_ID) {
+            return false;
+        }
+
+        AUDIT_LOGS_COLLECTOR.mStopRequested.set(true);
+        return IS_RUNNING.get();
+    }
+
+    private static class LogsCollectorJob implements Runnable {
+        private final JobService mAuditLogService;
+        private final JobParameters mParams;
+
+        LogsCollectorJob(JobService auditLogService, JobParameters params) {
+            mAuditLogService = auditLogService;
+            mParams = params;
+        }
+
+        @Override
+        public void run() {
+            IS_RUNNING.updateAndGet(
+                    isRunning -> {
+                        boolean done = AUDIT_LOGS_COLLECTOR.collect(AUDITD_TAG_CODE);
+                        if (done) {
+                            mAuditLogService.jobFinished(mParams, /* wantsReschedule= */ false);
+                        }
+                        return !done;
+                    });
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index d7b8495..b6d0ca1 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -2390,6 +2390,33 @@
         }
 
         @Override
+        public void sendCertificate(IBinder sessionToken, String host, int port,
+                Bundle certBundle, int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "sendCertificate(host=%s port=%d cert=%s)", host, port,
+                        certBundle);
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "sendCertificate");
+            SessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).sendCertificate(host, port, certBundle);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in sendCertificate", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public void notifyError(IBinder sessionToken, String errMsg, Bundle params, int userId) {
             if (DEBUG) {
                 Slogf.d(TAG, "notifyError(errMsg=%s)", errMsg);
@@ -4125,6 +4152,24 @@
         }
 
         @Override
+        public void onRequestCertificate(String host, int port) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slogf.d(TAG, "onRequestCertificate");
+                }
+                if (mSessionState.mSession == null || mSessionState.mClient == null) {
+                    return;
+                }
+                try {
+                    mSessionState.mClient.onRequestCertificate(host, port, mSessionState.mSeq);
+                } catch (RemoteException e) {
+                    Slogf.e(TAG, "error in onRequestCertificate", e);
+                }
+            }
+        }
+
+
+        @Override
         public void onAdRequest(AdRequest request) {
             synchronized (mLock) {
                 if (DEBUG) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
index 19fd9a9..9e1b5d2 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
@@ -96,7 +96,8 @@
             }
             if (populateOrientationPairs) {
                 int orientation = WallpaperManager.getOrientation(displaySize);
-                float newSurface = displaySize.x * displaySize.y * metric.getDensity();
+                float newSurface = displaySize.x * displaySize.y
+                        / (metric.getDensity() * metric.getDensity());
                 if (surface <= 0) {
                     surface = newSurface;
                     firstOrientation = orientation;
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
index 106be5f..4cc2c02 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
@@ -48,6 +48,7 @@
 import java.io.FileDescriptor;
 import java.util.Objects;
 import java.util.Set;
+import java.util.function.Consumer;
 
 /**
  * System service for managing sensing {@link AmbientContextEvent}s on Wearables.
@@ -191,9 +192,23 @@
         }
     }
 
+    private void callPerUserServiceIfExist(
+            Consumer<WearableSensingManagerPerUserService> serviceConsumer,
+            RemoteCallback statusCallback) {
+        int userId = UserHandle.getCallingUserId();
+        synchronized (mLock) {
+            WearableSensingManagerPerUserService service = getServiceForUserLocked(userId);
+            if (service == null) {
+                Slog.w(TAG, "Service not available for userId " + userId);
+                WearableSensingManagerPerUserService.notifyStatusCallback(statusCallback,
+                        WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+                return;
+            }
+            serviceConsumer.accept(service);
+        }
+    }
+
     private final class WearableSensingManagerInternal extends IWearableSensingManager.Stub {
-        final WearableSensingManagerPerUserService mService = getServiceForUserLocked(
-                UserHandle.getCallingUserId());
 
         @Override
         public void provideDataStream(
@@ -210,7 +225,9 @@
                         WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
                 return;
             }
-            mService.onProvideDataStream(parcelFileDescriptor, callback);
+            callPerUserServiceIfExist(
+                    service -> service.onProvideDataStream(parcelFileDescriptor, callback),
+                    callback);
         }
 
         @Override
@@ -229,7 +246,9 @@
                         WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
                 return;
             }
-            mService.onProvidedData(data, sharedMemory, callback);
+            callPerUserServiceIfExist(
+                    service -> service.onProvidedData(data, sharedMemory, callback),
+                    callback);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 2d584c4..f2d9bf8 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -462,17 +462,16 @@
         }
     }
 
-    // TODO(b/318327737): Remove parameter 't' when removing flag DRAW_IN_WM_LOCK.
-    void drawMagnifiedRegionBorderIfNeeded(int displayId, SurfaceControl.Transaction t) {
+    void drawMagnifiedRegionBorderIfNeeded(int displayId) {
         if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
             mAccessibilityTracing.logTrace(
                     TAG + ".drawMagnifiedRegionBorderIfNeeded",
                     FLAGS_MAGNIFICATION_CALLBACK,
-                    "displayId=" + displayId + "; transaction={" + t + "}");
+                    "displayId=" + displayId);
         }
         final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
         if (displayMagnifier != null) {
-            displayMagnifier.drawMagnifiedRegionBorderIfNeeded(t);
+            displayMagnifier.drawMagnifiedRegionBorderIfNeeded();
         }
         // Not relevant for the window observer.
     }
@@ -870,12 +869,12 @@
                     .sendToTarget();
         }
 
-        void drawMagnifiedRegionBorderIfNeeded(SurfaceControl.Transaction t) {
+        void drawMagnifiedRegionBorderIfNeeded() {
             if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
                 mAccessibilityTracing.logTrace(LOG_TAG + ".drawMagnifiedRegionBorderIfNeeded",
-                        FLAGS_MAGNIFICATION_CALLBACK, "transition={" + t + "}");
+                        FLAGS_MAGNIFICATION_CALLBACK);
             }
-            mMagnifedViewport.drawWindowIfNeeded(t);
+            mMagnifedViewport.drawWindowIfNeeded();
         }
 
         void dump(PrintWriter pw, String prefix) {
@@ -1121,14 +1120,6 @@
             }
 
             void setMagnifiedRegionBorderShown(boolean shown, boolean animate) {
-                if (ViewportWindow.DRAW_IN_WM_LOCK) {
-                    if (shown) {
-                        mFullRedrawNeeded = true;
-                        mOldMagnificationRegion.set(0, 0, 0, 0);
-                    }
-                    mWindow.setShown(shown, animate);
-                    return;
-                }
                 if (mWindow.setShown(shown, animate)) {
                     mFullRedrawNeeded = true;
                     // Clear the old region, so recomputeBounds will refresh the current region.
@@ -1151,12 +1142,8 @@
                 return mMagnificationSpec;
             }
 
-            void drawWindowIfNeeded(SurfaceControl.Transaction t) {
+            void drawWindowIfNeeded() {
                 recomputeBounds();
-                if (ViewportWindow.DRAW_IN_WM_LOCK) {
-                    mWindow.drawOrRemoveIfNeeded(t);
-                    return;
-                }
                 mWindow.postDrawIfNeeded();
             }
 
@@ -1187,8 +1174,6 @@
 
             private final class ViewportWindow implements Runnable {
                 private static final String SURFACE_TITLE = "Magnification Overlay";
-                // TODO(b/318327737): Remove if it is stable.
-                static final boolean DRAW_IN_WM_LOCK = !Flags.drawMagnifierBorderOutsideWmlock();
 
                 private final Region mBounds = new Region();
                 private final Rect mDirtyRect = new Rect();
@@ -1328,14 +1313,14 @@
 
                 @Override
                 public void run() {
-                    drawOrRemoveIfNeeded(mTransaction);
+                    drawOrRemoveIfNeeded();
                 }
 
                 /**
                  * This method must only be called by animation handler directly to make sure
                  * thread safe and there is no lock held outside.
                  */
-                private void drawOrRemoveIfNeeded(SurfaceControl.Transaction t) {
+                private void drawOrRemoveIfNeeded() {
                     // Drawing variables (alpha, dirty rect, and bounds) access is synchronized
                     // using WindowManagerGlobalLock. Grab copies of these values before
                     // drawing on the canvas so that drawing can be performed outside of the lock.
@@ -1343,7 +1328,7 @@
                     Rect drawingRect = null;
                     Region drawingBounds = null;
                     synchronized (mService.mGlobalLock) {
-                        if (!DRAW_IN_WM_LOCK && mBlastBufferQueue.mNativeObject == 0) {
+                        if (mBlastBufferQueue.mNativeObject == 0) {
                             // Complete removal since releaseSurface has been called.
                             if (mSurface.isValid()) {
                                 mTransaction.remove(mSurfaceControl).apply();
@@ -1388,16 +1373,8 @@
                         mPaint.setAlpha(alpha);
                         canvas.drawPath(drawingBounds.getBoundaryPath(), mPaint);
                         mSurface.unlockCanvasAndPost(canvas);
-                        if (DRAW_IN_WM_LOCK) {
-                            t.show(mSurfaceControl);
-                            return;
-                        }
                         showSurface = true;
                     } else {
-                        if (DRAW_IN_WM_LOCK) {
-                            t.hide(mSurfaceControl);
-                            return;
-                        }
                         showSurface = false;
                     }
 
@@ -1413,11 +1390,6 @@
                 @GuardedBy("mService.mGlobalLock")
                 void releaseSurface() {
                     mBlastBufferQueue.destroy();
-                    if (DRAW_IN_WM_LOCK) {
-                        mService.mTransactionFactory.get().remove(mSurfaceControl).apply();
-                        mSurface.release();
-                        return;
-                    }
                     // Post to perform cleanup on the thread which handles mSurface.
                     mService.mAnimationHandler.post(this);
                 }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 036f7b6..d9fa01e 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -141,6 +141,7 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SWITCH;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN;
 import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO;
 import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION;
 import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
@@ -991,6 +992,9 @@
     private CustomAppTransition mCustomOpenTransition;
     private CustomAppTransition mCustomCloseTransition;
 
+    /** Non-zero to pause dispatching configuration changes to the client. */
+    int mPauseConfigurationDispatchCount = 0;
+
     private final Runnable mPauseTimeoutRunnable = new Runnable() {
         @Override
         public void run() {
@@ -2631,10 +2635,20 @@
         if (snapshot == null) {
             return false;
         }
-        if (!snapshot.getTopActivityComponent().equals(mActivityComponent)) {
-            // Obsoleted snapshot.
-            return false;
-        }
+        return isSnapshotComponentCompatible(snapshot) && isSnapshotOrientationCompatible(snapshot);
+    }
+
+    /**
+     * Returns {@code true} if the top activity component of task snapshot equals to this activity.
+     */
+    boolean isSnapshotComponentCompatible(@NonNull TaskSnapshot snapshot) {
+        return snapshot.getTopActivityComponent().equals(mActivityComponent);
+    }
+
+    /**
+     * Returns {@code true} if the orientation of task snapshot is compatible with this activity.
+     */
+    boolean isSnapshotOrientationCompatible(@NonNull TaskSnapshot snapshot) {
         final int rotation = mDisplayContent.rotationForActivityInDifferentOrientation(this);
         final int currentRotation = task.getWindowConfiguration().getRotation();
         final int targetRotation = rotation != ROTATION_UNDEFINED
@@ -9276,6 +9290,59 @@
         }
     }
 
+    @Override
+    void dispatchConfigurationToChild(WindowState child, Configuration config) {
+        if (isConfigurationDispatchPaused()) {
+            return;
+        }
+        super.dispatchConfigurationToChild(child, config);
+    }
+
+    /**
+     * Pauses dispatch of configuration changes to the client. This includes any
+     * configuration-triggered lifecycle changes, WindowState configs, and surface changes. If
+     * a lifecycle change comes from another source (eg. stop), it will still run but will use the
+     * paused configuration.
+     *
+     * The main way this works is by blocking calls to {@link #updateReportedConfigurationAndSend}.
+     * That method is responsible for evaluating whether the activity needs to be relaunched and
+     * sending configurations.
+     */
+    void pauseConfigurationDispatch() {
+        ++mPauseConfigurationDispatchCount;
+        if (mPauseConfigurationDispatchCount == 1) {
+            ProtoLog.v(WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Pausing configuration dispatch for "
+                    + " %s", this);
+        }
+    }
+
+    /** @return `true` if configuration actually changed. */
+    boolean resumeConfigurationDispatch() {
+        --mPauseConfigurationDispatchCount;
+        if (mPauseConfigurationDispatchCount > 0) {
+            return false;
+        }
+        ProtoLog.v(WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Resuming configuration dispatch for %s", this);
+        if (mPauseConfigurationDispatchCount < 0) {
+            Slog.wtf(TAG, "Trying to resume non-paused configuration dispatch");
+            mPauseConfigurationDispatchCount = 0;
+            return false;
+        }
+        if (mLastReportedDisplayId == getDisplayId()
+                && getConfiguration().equals(mLastReportedConfiguration.getMergedConfiguration())) {
+            return false;
+        }
+        for (int i = getChildCount() - 1; i >= 0; --i) {
+            dispatchConfigurationToChild(getChildAt(i), getConfiguration());
+        }
+        updateReportedConfigurationAndSend();
+        return true;
+    }
+
+    boolean isConfigurationDispatchPaused() {
+        return mPauseConfigurationDispatchCount > 0;
+    }
+
     private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds,
             Rect containingBounds) {
         return applyAspectRatio(outBounds, containingAppBounds, containingBounds,
@@ -9525,6 +9592,17 @@
             return true;
         }
 
+        if (isConfigurationDispatchPaused()) {
+            return true;
+        }
+
+        return updateReportedConfigurationAndSend();
+    }
+
+    boolean updateReportedConfigurationAndSend() {
+        if (isConfigurationDispatchPaused()) {
+            Slog.wtf(TAG, "trying to update reported(client) config while dispatch is paused");
+        }
         ProtoLog.v(WM_DEBUG_CONFIGURATION, "Ensuring correct "
                 + "configuration: %s", this);
 
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index a4d15e0..83ccbdc 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -103,10 +103,6 @@
     static final boolean sPredictBackEnable =
             SystemProperties.getBoolean("persist.wm.debug.predictive_back", true);
 
-    static boolean isScreenshotEnabled() {
-        return SystemProperties.getInt("persist.wm.debug.predictive_back_screenshot", 0) != 0;
-    }
-
     // Notify focus window changed
     void onFocusChanged(WindowState newFocus) {
         mNavigationMonitor.onFocusWindowChanged(newFocus);
@@ -310,9 +306,11 @@
                     // keyguard locked and activities are unable to show when locked.
                     backType = BackNavigationInfo.TYPE_CALLBACK;
                 }
+            } else if (currentTask.mAtmService.getLockTaskController().isTaskLocked(currentTask)) {
+                // Do not predict if current task is in task locked.
+                backType = BackNavigationInfo.TYPE_CALLBACK;
             } else {
-                // TODO(208789724): Create single source of truth for this, maybe in
-                //  RootWindowContainer
+                // Check back-to-home or cross-task
                 prevTask = currentTask.mRootWindowContainer.getTask(t -> {
                     if (t.showToCurrentUser() && !t.mChildren.isEmpty()) {
                         final ActivityRecord ar = t.getTopNonFinishingActivity();
@@ -958,6 +956,18 @@
                 return;
             }
 
+            // Start fixed rotation for previous activity before create animation.
+            if (openingActivities.length == 1) {
+                final ActivityRecord next = openingActivities[0];
+                final DisplayContent dc = next.mDisplayContent;
+                dc.rotateInDifferentOrientationIfNeeded(next);
+                if (next.hasFixedRotationTransform()) {
+                    // Set the record so we can recognize it to continue to update display
+                    // orientation if the previous activity becomes the top later.
+                    dc.setFixedRotationLaunchingApp(next,
+                            next.getWindowConfiguration().getRotation());
+                }
+            }
             mOpenAnimAdaptor = new BackWindowAnimationAdaptorWrapper(true, mSwitchType, open);
             if (!mOpenAnimAdaptor.isValid()) {
                 Slog.w(TAG, "compose animations fail, skip");
@@ -1623,16 +1633,6 @@
         }
         activity.mLaunchTaskBehind = true;
 
-        // Handle fixed rotation launching app.
-        final DisplayContent dc = activity.mDisplayContent;
-        dc.rotateInDifferentOrientationIfNeeded(activity);
-        if (activity.hasFixedRotationTransform()) {
-            // Set the record so we can recognize it to continue to update display
-            // orientation if the previous activity becomes the top later.
-            dc.setFixedRotationLaunchingApp(activity,
-                    activity.getWindowConfiguration().getRotation());
-        }
-
         ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
                 "Setting Activity.mLauncherTaskBehind to true. Activity=%s", activity);
         activity.mTaskSupervisor.mStoppingActivities.remove(activity);
@@ -1700,21 +1700,38 @@
 
     static TaskSnapshot getSnapshot(@NonNull WindowContainer w,
             ActivityRecord[] visibleOpenActivities) {
+        TaskSnapshot snapshot = null;
         if (w.asTask() != null) {
             final Task task = w.asTask();
-            return task.mRootWindowContainer.mWindowManager.mTaskSnapshotController.getSnapshot(
+            snapshot = task.mRootWindowContainer.mWindowManager.mTaskSnapshotController.getSnapshot(
                     task.mTaskId, task.mUserId, false /* restoreFromDisk */,
                     false /* isLowResolution */);
-        }
-
-        if (w.asActivityRecord() != null) {
+        } else if (w.asActivityRecord() != null) {
             final ActivityRecord ar = w.asActivityRecord();
-            return ar.mWmService.mSnapshotController.mActivitySnapshotController
+            snapshot = ar.mWmService.mSnapshotController.mActivitySnapshotController
                     .getSnapshot(visibleOpenActivities);
         }
-        return null;
+
+        return isSnapshotCompatible(snapshot, visibleOpenActivities) ? snapshot : null;
     }
 
+    static boolean isSnapshotCompatible(@NonNull TaskSnapshot snapshot,
+            @NonNull ActivityRecord[] visibleOpenActivities) {
+        if (snapshot == null) {
+            return false;
+        }
+        boolean oneComponentMatch = false;
+        for (int i = visibleOpenActivities.length - 1; i >= 0; --i) {
+            final ActivityRecord ar = visibleOpenActivities[i];
+            if (!ar.isSnapshotOrientationCompatible(snapshot)) {
+                return false;
+            }
+            oneComponentMatch |= ar.isSnapshotComponentCompatible(snapshot);
+        }
+        return oneComponentMatch;
+    }
+
+
     void setWindowManager(WindowManagerService wm) {
         mWindowManagerService = wm;
         mAnimationHandler = new AnimationHandler(wm);
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 9ac4a5c..4681396 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -717,31 +717,6 @@
         boolean callerCanAllow = resultForCaller.allows() && !state.callerExplicitOptOut();
         boolean realCallerCanAllow = resultForRealCaller.allows()
                 && !state.realCallerExplicitOptOut();
-        if (callerCanAllow && realCallerCanAllow) {
-            // Both caller and real caller allow with system defined behavior
-            if (state.mBalAllowedByPiCreatorWithHardening.allowsBackgroundActivityStarts()) {
-                // Will be allowed even with BAL hardening.
-                if (DEBUG_ACTIVITY_STARTS) {
-                    Slog.d(TAG, "Activity start allowed by caller. "
-                            + state.dump());
-                }
-                return allowBasedOnCaller(state);
-            }
-            if (state.mBalAllowedByPiCreator.allowsBackgroundActivityStarts()) {
-                Slog.wtf(TAG,
-                        "With Android 15 BAL hardening this activity start may be blocked"
-                                + " if the PI creator upgrades target_sdk to 35+"
-                                + " AND the PI sender upgrades target_sdk to 34+! "
-                                + state.dump());
-                showBalRiskToast();
-                return allowBasedOnCaller(state);
-            }
-            Slog.wtf(TAG,
-                    "Without Android 15 BAL hardening this activity start would be allowed"
-                            + " (missing opt in by PI creator or sender)! "
-                            + state.dump());
-            return abortLaunch(state);
-        }
         if (callerCanAllow) {
             // Allowed before V by creator
             if (state.mBalAllowedByPiCreatorWithHardening.allowsBackgroundActivityStarts()) {
@@ -753,35 +728,29 @@
                 return allowBasedOnCaller(state);
             }
             if (state.mBalAllowedByPiCreator.allowsBackgroundActivityStarts()) {
-                Slog.wtf(TAG,
-                        "With Android 15 BAL hardening this activity start may be blocked"
+                Slog.wtf(TAG, "With Android 15 BAL hardening this activity start may be blocked"
                                 + " if the PI creator upgrades target_sdk to 35+! "
                                 + " (missing opt in by PI creator)! "
                                 + state.dump());
                 showBalRiskToast();
                 return allowBasedOnCaller(state);
             }
-            Slog.wtf(TAG,
-                    "Without Android 15 BAL hardening this activity start would be allowed"
-                            + " (missing opt in by PI creator)! "
-                            + state.dump());
-            return abortLaunch(state);
         }
         if (realCallerCanAllow) {
             // Allowed before U by sender
             if (state.mBalAllowedByPiSender.allowsBackgroundActivityStarts()) {
-                Slog.wtf(TAG,
-                        "With Android 14 BAL hardening this activity start will be blocked"
+                Slog.wtf(TAG, "With Android 14 BAL hardening this activity start will be blocked"
                                 + " if the PI sender upgrades target_sdk to 34+! "
                                 + " (missing opt in by PI sender)! "
                                 + state.dump());
                 showBalRiskToast();
                 return allowBasedOnRealCaller(state);
             }
-            Slog.wtf(TAG, "Without Android 14 BAL hardening this activity start would be allowed"
-                    + " (missing opt in by PI sender)! "
-                    + state.dump());
-            return abortLaunch(state);
+        }
+        // caller or real caller could start the activity, but would need to explicitly opt in
+        if (callerCanAllow || realCallerCanAllow) {
+            Slog.wtf(TAG, "Without BAL hardening this activity start would be allowed "
+                            + state.dump());
         }
         // neither the caller not the realCaller can allow or have explicitly opted out
         return abortLaunch(state);
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index f279689..0e2d3d1 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -1424,7 +1424,7 @@
 
     @VisibleForTesting
     boolean shouldShowLetterboxUi(WindowState mainWindow) {
-        if (mIsRelaunchingAfterRequestedOrientationChanged || !isSurfaceReadyToShow(mainWindow)) {
+        if (mIsRelaunchingAfterRequestedOrientationChanged) {
             return mLastShouldShowLetterboxUi;
         }
 
@@ -1442,13 +1442,6 @@
     }
 
     @VisibleForTesting
-    boolean isSurfaceReadyToShow(WindowState mainWindow) {
-        return mainWindow.isDrawn() // Regular case
-                // Waiting for relayoutWindow to call preserveSurface
-                || mainWindow.isDragResizeChanged();
-    }
-
-    @VisibleForTesting
     boolean isSurfaceVisible(WindowState mainWindow) {
         return mainWindow.isOnScreen() && (mActivityRecord.isVisible()
                 || mActivityRecord.isVisibleRequested());
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index f10a733..083872a 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -669,7 +669,7 @@
     }
 
     @Override
-    public Bundle sendWallpaperCommand(IBinder window, String action, int x, int y,
+    public void sendWallpaperCommand(IBinder window, String action, int x, int y,
             int z, Bundle extras, boolean sync) {
         synchronized (mService.mGlobalLock) {
             final long ident = Binder.clearCallingIdentity();
@@ -680,10 +680,9 @@
                 if (mCanAlwaysUpdateWallpaper
                         || windowState == wallpaperController.getWallpaperTarget()
                         || windowState == wallpaperController.getPrevWallpaperTarget()) {
-                    return wallpaperController.sendWindowWallpaperCommandUnchecked(
+                    wallpaperController.sendWindowWallpaperCommandUnchecked(
                             windowState, action, x, y, z, extras, sync);
                 }
-                return null;
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 0c6b174..b2b547e 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -45,7 +45,6 @@
 import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
 import static com.android.server.wm.ActivityRecord.State.PAUSED;
 import static com.android.server.wm.ActivityRecord.State.PAUSING;
@@ -89,7 +88,6 @@
 import android.content.res.Configuration;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.hardware.HardwareBuffer;
 import android.os.IBinder;
 import android.os.UserHandle;
 import android.util.DisplayMetrics;
@@ -99,7 +97,6 @@
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
 import android.window.ITaskFragmentOrganizer;
-import android.window.ScreenCapture;
 import android.window.TaskFragmentAnimationParams;
 import android.window.TaskFragmentInfo;
 import android.window.TaskFragmentOrganizerToken;
@@ -113,7 +110,6 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Set;
 import java.util.function.Consumer;
@@ -403,10 +399,6 @@
     /** For calculating app bounds, i.e. the area without the nav bar and display cutout. */
     private final Rect mTmpNonDecorBounds = new Rect();
 
-    //TODO(b/207481538) Remove once the infrastructure to support per-activity screenshot is
-    // implemented
-    HashMap<String, ScreenCapture.ScreenshotHardwareBuffer> mBackScreenshots = new HashMap<>();
-
     private final EnsureActivitiesVisibleHelper mEnsureActivitiesVisibleHelper =
             new EnsureActivitiesVisibleHelper(this);
 
@@ -2092,17 +2084,6 @@
         super.addChild(child, index);
 
         if (isAddingActivity && task != null) {
-            // TODO(b/207481538): temporary per-activity screenshoting
-            if (r != null && BackNavigationController.isScreenshotEnabled()) {
-                ProtoLog.v(WM_DEBUG_BACK_PREVIEW, "Screenshotting Activity %s",
-                        r.mActivityComponent.flattenToString());
-                Rect outBounds = r.getBounds();
-                ScreenCapture.ScreenshotHardwareBuffer backBuffer = ScreenCapture.captureLayers(
-                        r.mSurfaceControl,
-                        new Rect(0, 0, outBounds.width(), outBounds.height()),
-                        1f);
-                mBackScreenshots.put(r.mActivityComponent.flattenToString(), backBuffer);
-            }
             addingActivity.inHistory = true;
             task.onDescendantActivityAdded(taskHadActivity, activityType, addingActivity);
         }
@@ -2905,19 +2886,6 @@
         return !mCreatedByOrganizer || mIsRemovalRequested;
     }
 
-    @Nullable
-    HardwareBuffer getSnapshotForActivityRecord(@Nullable ActivityRecord r) {
-        if (!BackNavigationController.isScreenshotEnabled()) {
-            return null;
-        }
-        if (r != null && r.mActivityComponent != null) {
-            ScreenCapture.ScreenshotHardwareBuffer backBuffer =
-                    mBackScreenshots.get(r.mActivityComponent.flattenToString());
-            return backBuffer != null ? backBuffer.getHardwareBuffer() : null;
-        }
-        return null;
-    }
-
     @Override
     void removeChild(WindowContainer child) {
         removeChild(child, true /* removeSelfIfPossible */);
@@ -2926,13 +2894,6 @@
     void removeChild(WindowContainer child, boolean removeSelfIfPossible) {
         super.removeChild(child);
         final ActivityRecord r = child.asActivityRecord();
-        if (BackNavigationController.isScreenshotEnabled()) {
-            //TODO(b/207481538) Remove once the infrastructure to support per-activity screenshot is
-            // implemented
-            if (r != null) {
-                mBackScreenshots.remove(r.mActivityComponent.flattenToString());
-            }
-        }
         final WindowProcessController hostProcess = getOrganizerProcessIfDifferent(r);
         if (hostProcess != null) {
             hostProcess.removeEmbeddedActivity(r);
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 59e3350..d7b4a39 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -496,6 +496,9 @@
         if (mCollectingTransition != null && mCollectingTransition.isInTransientHide(task)) {
             return true;
         }
+        for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) {
+            if (mWaitingTransitions.get(i).isInTransientHide(task)) return true;
+        }
         for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
             if (mPlayingTransitions.get(i).isInTransientHide(task)) return true;
         }
@@ -506,6 +509,9 @@
         if (mCollectingTransition != null && mCollectingTransition.isTransientVisible(task)) {
             return true;
         }
+        for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) {
+            if (mWaitingTransitions.get(i).isTransientVisible(task)) return true;
+        }
         for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
             if (mPlayingTransitions.get(i).isTransientVisible(task)) return true;
         }
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 0fc62a7..399815b 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -641,11 +641,10 @@
         }
     }
 
-    Bundle sendWindowWallpaperCommandUnchecked(
+    void sendWindowWallpaperCommandUnchecked(
             WindowState window, String action, int x, int y, int z,
             Bundle extras, boolean sync) {
         sendWindowWallpaperCommand(action, x, y, z, extras, sync);
-        return null;
     }
 
     private void sendWindowWallpaperCommand(
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 750fd50..b43a454 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -146,10 +146,11 @@
             for (int i = 0; i < numDisplays; i++) {
                 final DisplayContent dc = root.getChildAt(i);
 
-                dc.checkAppWindowsReadyToShow();
+                if (!useShellTransition) {
+                    dc.checkAppWindowsReadyToShow();
+                }
                 if (accessibilityController.hasCallbacks()) {
-                    accessibilityController.drawMagnifiedRegionBorderIfNeeded(dc.mDisplayId,
-                            mTransaction);
+                    accessibilityController.drawMagnifiedRegionBorderIfNeeded(dc.mDisplayId);
                 }
 
                 if (dc.isAnimating(animationFlags, ANIMATION_TYPE_ALL)) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 56f2bc3..24e50c5 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -169,6 +169,7 @@
 import static com.android.server.wm.WindowStateProto.REMOVED;
 import static com.android.server.wm.WindowStateProto.REMOVE_ON_EXIT;
 import static com.android.server.wm.WindowStateProto.REQUESTED_HEIGHT;
+import static com.android.server.wm.WindowStateProto.REQUESTED_VISIBLE_TYPES;
 import static com.android.server.wm.WindowStateProto.REQUESTED_WIDTH;
 import static com.android.server.wm.WindowStateProto.STACK_ID;
 import static com.android.server.wm.WindowStateProto.SURFACE_INSETS;
@@ -3988,6 +3989,7 @@
         proto.write(FORCE_SEAMLESS_ROTATION, mForceSeamlesslyRotate);
         proto.write(HAS_COMPAT_SCALE, hasCompatScale());
         proto.write(GLOBAL_SCALE, mGlobalScale);
+        proto.write(REQUESTED_VISIBLE_TYPES, mRequestedVisibleTypes);
         for (Rect r : mKeepClearAreas) {
             r.dumpDebug(proto, KEEP_CLEAR_AREAS);
         }
@@ -5187,6 +5189,11 @@
         if (mSurfaceControl == null) {
             return;
         }
+        if (mActivityRecord != null && mActivityRecord.isConfigurationDispatchPaused()) {
+            // Don't update surface-position while dispatch paused. This is calculated from
+            // the server-side activity configuration so return early.
+            return;
+        }
 
         if ((mWmService.mWindowPlacerLocked.isLayoutDeferred() || isGoneForLayout())
                 && !mSurfacePlacementNeeded) {
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 5048cef..13e1ba78 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -639,9 +639,12 @@
 
     @Override
     void updateSurfacePosition(SurfaceControl.Transaction t) {
+        final ActivityRecord r = asActivityRecord();
+        if (r != null && r.isConfigurationDispatchPaused()) {
+            return;
+        }
         super.updateSurfacePosition(t);
         if (!mTransitionController.isShellTransitionsEnabled() && isFixedRotationTransforming()) {
-            final ActivityRecord r = asActivityRecord();
             final Task rootTask = r != null ? r.getRootTask() : null;
             // Don't transform the activity in PiP because the PiP task organizer will handle it.
             if (rootTask == null || !rootTask.inPinnedWindowingMode()) {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 8bc41af..cbc301b 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -306,7 +306,7 @@
     void setMotionClassifierEnabled(bool enabled);
     std::optional<std::string> getBluetoothAddress(int32_t deviceId);
     void setStylusButtonMotionEventsEnabled(bool enabled);
-    FloatPoint getMouseCursorPosition();
+    FloatPoint getMouseCursorPosition(int32_t displayId);
     void setStylusPointerIconEnabled(bool enabled);
 
     /* --- InputReaderPolicyInterface implementation --- */
@@ -1784,10 +1784,12 @@
             InputReaderConfiguration::Change::STYLUS_BUTTON_REPORTING);
 }
 
-FloatPoint NativeInputManager::getMouseCursorPosition() {
+FloatPoint NativeInputManager::getMouseCursorPosition(int32_t displayId) {
     if (ENABLE_POINTER_CHOREOGRAPHER) {
-        return mInputManager->getChoreographer().getMouseCursorPosition(ADISPLAY_ID_NONE);
+        return mInputManager->getChoreographer().getMouseCursorPosition(displayId);
     }
+    // To maintain the status-quo, the displayId parameter (used when PointerChoreographer is
+    // enabled) is ignored in the old pipeline.
     std::scoped_lock _l(mLock);
     const auto pc = mLocked.legacyPointerController.lock();
     if (!pc) return {AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION};
@@ -2751,9 +2753,10 @@
     im->setStylusButtonMotionEventsEnabled(enabled);
 }
 
-static jfloatArray nativeGetMouseCursorPosition(JNIEnv* env, jobject nativeImplObj) {
+static jfloatArray nativeGetMouseCursorPosition(JNIEnv* env, jobject nativeImplObj,
+                                                jint displayId) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
-    const auto p = im->getMouseCursorPosition();
+    const auto p = im->getMouseCursorPosition(displayId);
     const std::array<float, 2> arr = {{p.x, p.y}};
     jfloatArray outArr = env->NewFloatArray(2);
     env->SetFloatArrayRegion(outArr, 0, arr.size(), arr.data());
@@ -2775,6 +2778,15 @@
     }
 }
 
+static void nativeSetAccessibilitySlowKeysThreshold(JNIEnv* env, jobject nativeImplObj,
+                                                    jint thresholdTimeMs) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+    if (ENABLE_INPUT_FILTER_RUST) {
+        im->getInputManager()->getInputFilter().setAccessibilitySlowKeysThreshold(
+                static_cast<nsecs_t>(thresholdTimeMs) * 1000000);
+    }
+}
+
 static void nativeSetAccessibilityStickyKeysEnabled(JNIEnv* env, jobject nativeImplObj,
                                                     jboolean enabled) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -2883,10 +2895,12 @@
         {"getBluetoothAddress", "(I)Ljava/lang/String;", (void*)nativeGetBluetoothAddress},
         {"setStylusButtonMotionEventsEnabled", "(Z)V",
          (void*)nativeSetStylusButtonMotionEventsEnabled},
-        {"getMouseCursorPosition", "()[F", (void*)nativeGetMouseCursorPosition},
+        {"getMouseCursorPosition", "(I)[F", (void*)nativeGetMouseCursorPosition},
         {"setStylusPointerIconEnabled", "(Z)V", (void*)nativeSetStylusPointerIconEnabled},
         {"setAccessibilityBounceKeysThreshold", "(I)V",
          (void*)nativeSetAccessibilityBounceKeysThreshold},
+        {"setAccessibilitySlowKeysThreshold", "(I)V",
+         (void*)nativeSetAccessibilitySlowKeysThreshold},
         {"setAccessibilityStickyKeysEnabled", "(Z)V",
          (void*)nativeSetAccessibilityStickyKeysEnabled},
 };
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 86ad494..2b8bcc7 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -203,6 +203,7 @@
 import com.android.server.security.KeyAttestationApplicationIdProviderService;
 import com.android.server.security.KeyChainSystemService;
 import com.android.server.security.rkp.RemoteProvisioningService;
+import com.android.server.selinux.SelinuxAuditLogsService;
 import com.android.server.sensorprivacy.SensorPrivacyService;
 import com.android.server.sensors.SensorService;
 import com.android.server.signedconfig.SignedConfigService;
@@ -433,6 +434,9 @@
     private static final String ROLE_SERVICE_CLASS = "com.android.role.RoleService";
     private static final String GAME_MANAGER_SERVICE_CLASS =
             "com.android.server.app.GameManagerService$Lifecycle";
+    private static final String ENHANCED_CONFIRMATION_SERVICE_CLASS =
+            "com.android.ecm.EnhancedConfirmationService";
+
     private static final String UWB_APEX_SERVICE_JAR_PATH =
             "/apex/com.android.uwb/javalib/service-uwb.jar";
     private static final String UWB_SERVICE_CLASS = "com.android.server.uwb.UwbService";
@@ -1592,6 +1596,12 @@
             mSystemServiceManager.startService(DropBoxManagerService.class);
             t.traceEnd();
 
+            if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()) {
+                t.traceBegin("StartEnhancedConfirmationService");
+                mSystemServiceManager.startService(ENHANCED_CONFIRMATION_SERVICE_CLASS);
+                t.traceEnd();
+            }
+
             // Grants default permissions and defines roles
             t.traceBegin("StartRoleManagerService");
             LocalManagerRegistry.addManager(RoleServicePlatformHelper.class,
@@ -2609,6 +2619,14 @@
                 t.traceEnd();
             }
 
+            t.traceBegin("StartSelinuxAuditLogsService");
+            try {
+                SelinuxAuditLogsService.schedule(context);
+            } catch (Throwable e) {
+                reportWtf("starting SelinuxAuditLogsService", e);
+            }
+            t.traceEnd();
+
             // LauncherAppsService uses ShortcutService.
             t.traceBegin("StartShortcutServiceLifecycle");
             mSystemServiceManager.startService(ShortcutService.Lifecycle.class);
diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
index a0fb013..3284cf1 100644
--- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
@@ -94,7 +94,9 @@
         isSystemUpdated: Boolean
     ) {
         packageNames.forEachIndexed { _, packageName ->
-            val packageState = newState.externalState.packageStates[packageName]!!
+            // The package may still be removed even if it was once notified as installed.
+            val packageState = newState.externalState.packageStates[packageName]
+                ?: return@forEachIndexed
             trimPermissionStates(packageState.appId)
         }
     }
@@ -127,7 +129,10 @@
         val packageState = newState.externalState.packageStates[packageName] ?: return
         val androidPackage = packageState.androidPackage ?: return
         val appId = packageState.appId
-        val appIdPermissionFlags = newState.userStates[userId]!!.appIdDevicePermissionFlags
+        // The user may happen removed due to DeletePackageHelper.removeUnusedPackagesLPw() calling
+        // deletePackageX() asynchronously.
+        val userState = newState.userStates[userId] ?: return
+        val devicePermissionFlags = userState.appIdDevicePermissionFlags[appId] ?: return
         androidPackage.requestedPermissions.forEach { permissionName ->
             val isRequestedByOtherPackages =
                 anyPackageInAppId(appId) {
@@ -137,7 +142,7 @@
             if (isRequestedByOtherPackages) {
                 return@forEach
             }
-            appIdPermissionFlags[appId]?.forEachIndexed { _, deviceId, _ ->
+            devicePermissionFlags.forEachIndexed { _, deviceId, _ ->
                 setPermissionFlags(appId, deviceId, userId, permissionName, 0)
             }
         }
@@ -245,6 +250,13 @@
         flagMask: Int,
         flagValues: Int
     ): Boolean {
+        if (userId !in newState.userStates) {
+            // Despite that we check UserManagerInternal.exists() in PermissionService, we may still
+            // sometimes get race conditions between that check and the actual mutateState() call.
+            // This should rarely happen but at least we should not crash.
+            Slog.e(LOG_TAG, "Unable to update permission flags for missing user $userId")
+            return false
+        }
         val oldFlags =
             newState.userStates[userId]!!
                 .appIdDevicePermissionFlags[appId]
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java
index 30afa72..b9f1ea0 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java
@@ -16,13 +16,14 @@
 package com.android.server.inputmethod;
 
 import static com.android.server.inputmethod.ClientController.ClientControllerCallback;
-import static com.android.server.inputmethod.ClientController.ClientState;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertThrows;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyLong;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -53,6 +54,7 @@
     private static final int ANY_DISPLAY_ID = Display.DEFAULT_DISPLAY;
     private static final int ANY_CALLER_UID = 1;
     private static final int ANY_CALLER_PID = 1;
+    private static final String SOME_PACKAGE_NAME = "some.package";
 
     @Rule
     public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
@@ -81,7 +83,8 @@
     }
 
     @Test
-    // TODO(b/314150112): Enable host side mode for this test once b/315544364 is fixed.
+    // TODO(b/314150112): Enable host side mode for this test once Ravenwood is enabled for
+    //  inputmethod server classes.
     @IgnoreUnderRavenwood(blockedBy = {InputBinding.class, IInputMethodClientInvoker.class})
     public void testAddClient_cannotAddTheSameClientTwice() {
         var invoker = IInputMethodClientInvoker.create(mClient, mHandler);
@@ -103,7 +106,8 @@
     }
 
     @Test
-    // TODO(b/314150112): Enable host side mode for this test once b/315544364 is fixed.
+    // TODO(b/314150112): Enable host side mode for this test once Ravenwood is enabled for
+    //  inputmethod server classes.
     @IgnoreUnderRavenwood(blockedBy = {InputBinding.class, IInputMethodClientInvoker.class})
     public void testAddClient() throws Exception {
         synchronized (ImfLock.class) {
@@ -117,7 +121,8 @@
     }
 
     @Test
-    // TODO(b/314150112): Enable host side mode for this test once b/315544364 is fixed.
+    // TODO(b/314150112): Enable host side mode for this test once Ravenwood is enabled for
+    //  inputmethod server classes.
     @IgnoreUnderRavenwood(blockedBy = {InputBinding.class, IInputMethodClientInvoker.class})
     public void testRemoveClient() {
         var callback = new TestClientControllerCallback();
@@ -137,6 +142,36 @@
         assertThat(removed).isSameInstanceAs(added);
     }
 
+    @Test
+    // TODO(b/314150112): Enable host side mode for this test once Ravenwood is enabled for
+    //  inputmethod server classes and updated to newer Mockito with static mock support (mock
+    //  InputMethodUtils#checkIfPackageBelongsToUid instead of PackageManagerInternal#isSameApp)
+    @IgnoreUnderRavenwood(blockedBy = {InputMethodUtils.class})
+    public void testVerifyClientAndPackageMatch() {
+        when(mMockPackageManagerInternal.isSameApp(eq(SOME_PACKAGE_NAME),  /* flags= */
+                anyLong(), eq(ANY_CALLER_UID), /* userId= */ anyInt())).thenReturn(true);
+
+        synchronized (ImfLock.class) {
+            var invoker = IInputMethodClientInvoker.create(mClient, mHandler);
+            mController.addClient(invoker, mConnection, ANY_DISPLAY_ID, ANY_CALLER_UID,
+                    ANY_CALLER_PID);
+            assertThat(
+                    mController.verifyClientAndPackageMatch(mClient, SOME_PACKAGE_NAME)).isTrue();
+        }
+    }
+
+    @Test
+    public void testVerifyClientAndPackageMatch_unknownClient() {
+        synchronized (ImfLock.class) {
+            assertThrows(IllegalArgumentException.class,
+                    () -> {
+                        synchronized (ImfLock.class) {
+                            mController.verifyClientAndPackageMatch(mClient, SOME_PACKAGE_NAME);
+                        }
+                    });
+        }
+    }
+
     private static class TestClientControllerCallback implements ClientControllerCallback {
 
         private final CountDownLatch mLatch = new CountDownLatch(1);
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index 438bea4..1c71a62 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -22,7 +22,6 @@
 import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SOFT_INPUT;
 import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SWITCH_USER;
 import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_SOFT_INPUT;
-import static com.android.server.inputmethod.ClientController.ClientState;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_EXPLICIT;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_NOT_ALWAYS;
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 93a2eef..28471b3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -180,6 +180,7 @@
         JobSchedulerService.sElapsedRealtimeClock =
                 Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC);
         // Initialize real objects.
+        doReturn(Long.MAX_VALUE).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(any());
         ArgumentCaptor<BroadcastReceiver> receiverCaptor =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
         mFlexibilityController = new FlexibilityController(mJobSchedulerService,
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 5bec903..656bc71 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -556,7 +556,7 @@
     @Test
     public void testCreateUserWithLongName_TruncatesName() {
         UserInfo user = mUms.createUserWithThrow(generateLongString(), USER_TYPE_FULL_SECONDARY, 0);
-        assertThat(user.name.length()).isEqualTo(500);
+        assertThat(user.name.length()).isEqualTo(UserManager.MAX_USER_NAME_LENGTH);
         UserInfo user1 = mUms.createUserWithThrow("Test", USER_TYPE_FULL_SECONDARY, 0);
         assertThat(user1.name.length()).isEqualTo(4);
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/selinux/RateLimiterTest.java b/services/tests/mockingservicestests/src/com/android/server/selinux/RateLimiterTest.java
new file mode 100644
index 0000000..01c7fbe
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/selinux/RateLimiterTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.selinux;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.os.Clock;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class RateLimiterTest {
+
+    private final MockClock mMockClock = new MockClock();
+
+    @Test
+    public void testRateLimiter_1QPS() {
+        RateLimiter rateLimiter = new RateLimiter(mMockClock, Duration.ofSeconds(1));
+
+        // First acquire is granted.
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        // Next acquire is negated because it's too soon.
+        assertThat(rateLimiter.tryAcquire()).isFalse();
+        // Wait >=1 seconds.
+        mMockClock.currentTimeMillis += Duration.ofSeconds(1).toMillis();
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+    }
+
+    @Test
+    public void testRateLimiter_3QPS() {
+        RateLimiter rateLimiter =
+                new RateLimiter(
+                        mMockClock,
+                        Duration.ofSeconds(1).dividedBy(3).truncatedTo(ChronoUnit.MILLIS));
+
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        mMockClock.currentTimeMillis += Duration.ofSeconds(1).dividedBy(2).toMillis();
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        mMockClock.currentTimeMillis += Duration.ofSeconds(1).dividedBy(3).toMillis();
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        mMockClock.currentTimeMillis += Duration.ofSeconds(1).dividedBy(4).toMillis();
+        assertThat(rateLimiter.tryAcquire()).isFalse();
+    }
+
+    @Test
+    public void testRateLimiter_infiniteQPS() {
+        RateLimiter rateLimiter = new RateLimiter(mMockClock, Duration.ofMillis(0));
+
+        // so many permits.
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+
+        mMockClock.currentTimeMillis += Duration.ofSeconds(10).toMillis();
+        // still so many permits.
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+
+        mMockClock.currentTimeMillis += Duration.ofDays(-10).toMillis();
+        // only going backwards in time you will stop the permits.
+        assertThat(rateLimiter.tryAcquire()).isFalse();
+        assertThat(rateLimiter.tryAcquire()).isFalse();
+        assertThat(rateLimiter.tryAcquire()).isFalse();
+    }
+
+    @Test
+    public void testRateLimiter_negativeQPS() {
+        RateLimiter rateLimiter = new RateLimiter(mMockClock, Duration.ofMillis(-10));
+
+        // Negative QPS is effectively turning of the rate limiter.
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        mMockClock.currentTimeMillis += Duration.ofSeconds(1000).toMillis();
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+    }
+
+    private static final class MockClock extends Clock {
+
+        public long currentTimeMillis = 0;
+
+        @Override
+        public long currentTimeMillis() {
+            return currentTimeMillis;
+        }
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java b/services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java
new file mode 100644
index 0000000..b36c9bd
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java
@@ -0,0 +1,202 @@
+/*
+ * 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.selinux;
+
+import static com.android.server.selinux.SelinuxAuditLogBuilder.PATH_MATCHER;
+import static com.android.server.selinux.SelinuxAuditLogBuilder.SCONTEXT_MATCHER;
+import static com.android.server.selinux.SelinuxAuditLogBuilder.TCONTEXT_MATCHER;
+import static com.android.server.selinux.SelinuxAuditLogBuilder.toCategories;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.server.selinux.SelinuxAuditLogBuilder.SelinuxAuditLog;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SelinuxAuditLogsBuilderTest {
+
+    private final SelinuxAuditLogBuilder mAuditLogBuilder = new SelinuxAuditLogBuilder();
+
+    @Test
+    public void testMatcher_scontext() {
+        assertThat(SCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0").matches()).isTrue();
+        assertThat(SCONTEXT_MATCHER.group("stype")).isEqualTo("sdk_sandbox_audit");
+        assertThat(SCONTEXT_MATCHER.group("scategories")).isNull();
+
+        assertThat(SCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0:c123,c456").matches()).isTrue();
+        assertThat(SCONTEXT_MATCHER.group("stype")).isEqualTo("sdk_sandbox_audit");
+        assertThat(toCategories(SCONTEXT_MATCHER.group("scategories")))
+                .isEqualTo(new int[] {123, 456});
+
+        assertThat(SCONTEXT_MATCHER.reset("u:r:not_sdk_sandbox:s0").matches()).isFalse();
+        assertThat(SCONTEXT_MATCHER.reset("u:object_r:sdk_sandbox_audit:s0").matches()).isFalse();
+        assertThat(SCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0:p123").matches()).isFalse();
+    }
+
+    @Test
+    public void testMatcher_tcontext() {
+        assertThat(TCONTEXT_MATCHER.reset("u:object_r:target_type:s0").matches()).isTrue();
+        assertThat(TCONTEXT_MATCHER.group("ttype")).isEqualTo("target_type");
+        assertThat(TCONTEXT_MATCHER.group("tcategories")).isNull();
+
+        assertThat(TCONTEXT_MATCHER.reset("u:object_r:target_type2:s0:c666").matches()).isTrue();
+        assertThat(TCONTEXT_MATCHER.group("ttype")).isEqualTo("target_type2");
+        assertThat(toCategories(TCONTEXT_MATCHER.group("tcategories"))).isEqualTo(new int[] {666});
+
+        assertThat(TCONTEXT_MATCHER.reset("u:r:target_type:s0").matches()).isFalse();
+        assertThat(TCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0:x456").matches()).isFalse();
+    }
+
+    @Test
+    public void testMatcher_path() {
+        assertThat(PATH_MATCHER.reset("\"/data\"").matches()).isTrue();
+        assertThat(PATH_MATCHER.group("path")).isEqualTo("/data");
+        assertThat(PATH_MATCHER.reset("\"/data/local\"").matches()).isTrue();
+        assertThat(PATH_MATCHER.group("path")).isEqualTo("/data/local");
+        assertThat(PATH_MATCHER.reset("\"/data/local/tmp\"").matches()).isTrue();
+        assertThat(PATH_MATCHER.group("path")).isEqualTo("/data/local");
+
+        assertThat(PATH_MATCHER.reset("\"/data/local").matches()).isFalse();
+        assertThat(PATH_MATCHER.reset("\"_data_local\"").matches()).isFalse();
+    }
+
+    @Test
+    public void testSelinuxAuditLogsBuilder_noOptionals() {
+        mAuditLogBuilder.reset(
+                "granted { p } scontext=u:r:sdk_sandbox_audit:s0 tcontext=u:object_r:t:s0"
+                        + " tclass=c");
+        assertAuditLog(
+                mAuditLogBuilder.build(), true, new String[] {"p"}, "sdk_sandbox_audit", "t", "c");
+
+        mAuditLogBuilder.reset(
+                "tclass=c2 granted { p2 } tcontext=u:object_r:t2:s0"
+                        + " scontext=u:r:sdk_sandbox_audit:s0");
+        assertAuditLog(
+                mAuditLogBuilder.build(),
+                true,
+                new String[] {"p2"},
+                "sdk_sandbox_audit",
+                "t2",
+                "c2");
+    }
+
+    @Test
+    public void testSelinuxAuditLogsBuilder_withCategories() {
+        mAuditLogBuilder.reset(
+                "granted { p } scontext=u:r:sdk_sandbox_audit:s0:c123"
+                        + " tcontext=u:object_r:t:s0:c456,c666 tclass=c");
+        assertAuditLog(
+                mAuditLogBuilder.build(),
+                true,
+                new String[] {"p"},
+                "sdk_sandbox_audit",
+                new int[] {123},
+                "t",
+                new int[] {456, 666},
+                "c",
+                null,
+                false);
+    }
+
+    @Test
+    public void testSelinuxAuditLogsBuilder_withPath() {
+        mAuditLogBuilder.reset(
+                "granted { p } scontext=u:r:sdk_sandbox_audit:s0 path=\"/very/long/path\""
+                        + " tcontext=u:object_r:t:s0 tclass=c");
+        assertAuditLog(
+                mAuditLogBuilder.build(),
+                true,
+                new String[] {"p"},
+                "sdk_sandbox_audit",
+                null,
+                "t",
+                null,
+                "c",
+                "/very/long",
+                false);
+    }
+
+    @Test
+    public void testSelinuxAuditLogsBuilder_withPermissive() {
+        mAuditLogBuilder.reset(
+                "granted { p } scontext=u:r:sdk_sandbox_audit:s0 permissive=0"
+                        + " tcontext=u:object_r:t:s0 tclass=c");
+        assertAuditLog(
+                mAuditLogBuilder.build(),
+                true,
+                new String[] {"p"},
+                "sdk_sandbox_audit",
+                null,
+                "t",
+                null,
+                "c",
+                null,
+                false);
+
+        mAuditLogBuilder.reset(
+                "granted { p } scontext=u:r:sdk_sandbox_audit:s0 tcontext=u:object_r:t:s0 tclass=c"
+                        + " permissive=1");
+        assertAuditLog(
+                mAuditLogBuilder.build(),
+                true,
+                new String[] {"p"},
+                "sdk_sandbox_audit",
+                null,
+                "t",
+                null,
+                "c",
+                null,
+                true);
+    }
+
+    private void assertAuditLog(
+            SelinuxAuditLog auditLog,
+            boolean granted,
+            String[] permissions,
+            String sType,
+            String tType,
+            String tClass) {
+        assertAuditLog(
+                auditLog, granted, permissions, sType, null, tType, null, tClass, null, false);
+    }
+
+    private void assertAuditLog(
+            SelinuxAuditLog auditLog,
+            boolean granted,
+            String[] permissions,
+            String sType,
+            int[] sCategories,
+            String tType,
+            int[] tCategories,
+            String tClass,
+            String path,
+            boolean permissive) {
+        assertThat(auditLog).isNotNull();
+        assertThat(auditLog.mGranted).isEqualTo(granted);
+        assertThat(auditLog.mPermissions).isEqualTo(permissions);
+        assertThat(auditLog.mSType).isEqualTo(sType);
+        assertThat(auditLog.mSCategories).isEqualTo(sCategories);
+        assertThat(auditLog.mTType).isEqualTo(tType);
+        assertThat(auditLog.mTCategories).isEqualTo(tCategories);
+        assertThat(auditLog.mTClass).isEqualTo(tClass);
+        assertThat(auditLog.mPath).isEqualTo(path);
+        assertThat(auditLog.mPermissive).isEqualTo(permissive);
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java b/services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java
new file mode 100644
index 0000000..9758ea5
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java
@@ -0,0 +1,644 @@
+/*
+ * 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.selinux;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static com.google.common.truth.Truth.assertThat;
+
+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.Mockito.never;
+import static org.mockito.Mockito.times;
+
+import android.util.EventLog;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.os.Clock;
+import com.android.internal.util.FrameworkStatsLog;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoSession;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
+@RunWith(AndroidJUnit4.class)
+public class SelinuxAuditLogsCollectorTest {
+
+    // Fake tag to use for testing
+    private static final int ANSWER_TAG = 42;
+
+    private final MockClock mClock = new MockClock();
+
+    private final SelinuxAuditLogsCollector mSelinuxAutidLogsCollector =
+            // Ignore rate limiting for tests
+            new SelinuxAuditLogsCollector(
+                    new RateLimiter(mClock, /* window= */ Duration.ofMillis(0)),
+                    new QuotaLimiter(
+                            mClock, /* windowSize= */ Duration.ofHours(1), /* maxPermits= */ 5));
+
+    private MockitoSession mMockitoSession;
+
+    @Before
+    public void setUp() {
+        // move the clock forward for the limiters.
+        mClock.currentTimeMillis += Duration.ofHours(1).toMillis();
+        // Ignore what was written in the event logs by previous tests.
+        mSelinuxAutidLogsCollector.mLastWrite = Instant.now();
+
+        mMockitoSession =
+                mockitoSession().initMocks(this).mockStatic(FrameworkStatsLog.class).startMocking();
+    }
+
+    @After
+    public void tearDown() {
+        mMockitoSession.finishMocking();
+    }
+
+    @Test
+    public void testWriteSdkSandboxAuditLogs() {
+        writeTestLog("granted", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm1", "sdk_sandbox_audit", "ttype1", "tclass1");
+
+        boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+        assertThat(done).isTrue();
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.SELINUX_AUDIT_LOG,
+                                true,
+                                new String[] {"perm"},
+                                "sdk_sandbox_audit",
+                                null,
+                                "ttype",
+                                null,
+                                "tclass",
+                                null,
+                                false));
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.SELINUX_AUDIT_LOG,
+                                false,
+                                new String[] {"perm1"},
+                                "sdk_sandbox_audit",
+                                null,
+                                "ttype1",
+                                null,
+                                "tclass1",
+                                null,
+                                false));
+    }
+
+    @Test
+    public void testWriteSdkSandboxAuditLogs_multiplePerms() {
+        writeTestLog("denied", "perm1 perm2", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm3 perm4", "sdk_sandbox_audit", "ttype", "tclass");
+
+        boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+        assertThat(done).isTrue();
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.SELINUX_AUDIT_LOG,
+                                false,
+                                new String[] {"perm1", "perm2"},
+                                "sdk_sandbox_audit",
+                                null,
+                                "ttype",
+                                null,
+                                "tclass",
+                                null,
+                                false));
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.SELINUX_AUDIT_LOG,
+                                false,
+                                new String[] {"perm3", "perm4"},
+                                "sdk_sandbox_audit",
+                                null,
+                                "ttype",
+                                null,
+                                "tclass",
+                                null,
+                                false));
+    }
+
+    @Test
+    public void testWriteSdkSandboxAuditLogs_withPaths() {
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "/good/path");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "/very/long/path");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "/short_path");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "not_a_path");
+
+        boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+        assertThat(done).isTrue();
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.SELINUX_AUDIT_LOG,
+                                false,
+                                new String[] {"perm"},
+                                "sdk_sandbox_audit",
+                                null,
+                                "ttype",
+                                null,
+                                "tclass",
+                                "/good/path",
+                                false));
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.SELINUX_AUDIT_LOG,
+                                false,
+                                new String[] {"perm"},
+                                "sdk_sandbox_audit",
+                                null,
+                                "ttype",
+                                null,
+                                "tclass",
+                                "/very/long",
+                                false));
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.SELINUX_AUDIT_LOG,
+                                false,
+                                new String[] {"perm"},
+                                "sdk_sandbox_audit",
+                                null,
+                                "ttype",
+                                null,
+                                "tclass",
+                                "/short_path",
+                                false));
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.SELINUX_AUDIT_LOG,
+                                false,
+                                new String[] {"perm"},
+                                "sdk_sandbox_audit",
+                                null,
+                                "ttype",
+                                null,
+                                "tclass",
+                                null,
+                                false));
+    }
+
+    @Test
+    public void testWriteSdkSandboxAuditLogs_withCategories() {
+        writeTestLog(
+                "denied", "perm", "sdk_sandbox_audit", new int[] {123}, "ttype", null, "tclass");
+        writeTestLog(
+                "denied",
+                "perm",
+                "sdk_sandbox_audit",
+                new int[] {123, 456},
+                "ttype",
+                null,
+                "tclass");
+        writeTestLog(
+                "denied", "perm", "sdk_sandbox_audit", null, "ttype", new int[] {666}, "tclass");
+        writeTestLog(
+                "denied",
+                "perm",
+                "sdk_sandbox_audit",
+                new int[] {123, 456},
+                "ttype",
+                new int[] {666, 777},
+                "tclass");
+
+        boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+        assertThat(done).isTrue();
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.SELINUX_AUDIT_LOG,
+                                false,
+                                new String[] {"perm"},
+                                "sdk_sandbox_audit",
+                                new int[] {123},
+                                "ttype",
+                                null,
+                                "tclass",
+                                null,
+                                false));
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.SELINUX_AUDIT_LOG,
+                                false,
+                                new String[] {"perm"},
+                                "sdk_sandbox_audit",
+                                new int[] {123, 456},
+                                "ttype",
+                                null,
+                                "tclass",
+                                null,
+                                false));
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.SELINUX_AUDIT_LOG,
+                                false,
+                                new String[] {"perm"},
+                                "sdk_sandbox_audit",
+                                null,
+                                "ttype",
+                                new int[] {666},
+                                "tclass",
+                                null,
+                                false));
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.SELINUX_AUDIT_LOG,
+                                false,
+                                new String[] {"perm"},
+                                "sdk_sandbox_audit",
+                                new int[] {123, 456},
+                                "ttype",
+                                new int[] {666, 777},
+                                "tclass",
+                                null,
+                                false));
+    }
+
+    @Test
+    public void testWriteSdkSandboxAuditLogs_withPathAndCategories() {
+        writeTestLog(
+                "denied",
+                "perm",
+                "sdk_sandbox_audit",
+                new int[] {123},
+                "ttype",
+                new int[] {666},
+                "tclass",
+                "/a/path");
+
+        boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+        assertThat(done).isTrue();
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.SELINUX_AUDIT_LOG,
+                                false,
+                                new String[] {"perm"},
+                                "sdk_sandbox_audit",
+                                new int[] {123},
+                                "ttype",
+                                new int[] {666},
+                                "tclass",
+                                "/a/path",
+                                false));
+    }
+
+    @Test
+    public void testWriteSdkSandboxAuditLogs_permissive() {
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", true);
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", false);
+
+        boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+        assertThat(done).isTrue();
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.SELINUX_AUDIT_LOG,
+                                false,
+                                new String[] {"perm"},
+                                "sdk_sandbox_audit",
+                                null,
+                                "ttype",
+                                null,
+                                "tclass",
+                                null,
+                                false),
+                times(2));
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.SELINUX_AUDIT_LOG,
+                                false,
+                                new String[] {"perm"},
+                                "sdk_sandbox_audit",
+                                null,
+                                "ttype",
+                                null,
+                                "tclass",
+                                null,
+                                true));
+    }
+
+    @Test
+    public void testNotWriteAuditLogs_notSdkSandbox() {
+        writeTestLog("denied", "perm", "stype", "ttype", "tclass");
+
+        boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+        assertThat(done).isTrue();
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                anyInt(),
+                                anyBoolean(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyBoolean()),
+                never());
+    }
+
+    @Test
+    public void testWriteSdkSandboxAuditLogs_upToQuota() {
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        // These are not pushed.
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+
+        boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+        assertThat(done).isTrue();
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                anyInt(),
+                                anyBoolean(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyBoolean()),
+                times(5));
+    }
+
+    @Test
+    public void testWriteSdkSandboxAuditLogs_resetQuota() {
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+
+        boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+        assertThat(done).isTrue();
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                anyInt(),
+                                anyBoolean(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyBoolean()),
+                times(5));
+
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        // move the clock forward to reset the quota limiter.
+        mClock.currentTimeMillis += Duration.ofHours(1).toMillis();
+        done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+        assertThat(done).isTrue();
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                anyInt(),
+                                anyBoolean(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyBoolean()),
+                times(10));
+    }
+
+    @Test
+    public void testNotWriteAuditLogs_stopRequested() {
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        // These are not pushed.
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+
+        mSelinuxAutidLogsCollector.mStopRequested.set(true);
+        boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+        assertThat(done).isFalse();
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                anyInt(),
+                                anyBoolean(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyBoolean()),
+                never());
+
+        mSelinuxAutidLogsCollector.mStopRequested.set(false);
+        done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+        assertThat(done).isTrue();
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                anyInt(),
+                                anyBoolean(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyBoolean()),
+                times(5));
+    }
+
+    @Test
+    public void testAuditLogs_resumeJobDoesNotExceedLimit() {
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        mSelinuxAutidLogsCollector.mStopRequested.set(true);
+
+        boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+        assertThat(done).isFalse();
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                anyInt(),
+                                anyBoolean(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyBoolean()),
+                never());
+    }
+
+    private static void writeTestLog(
+            String granted, String permissions, String sType, String tType, String tClass) {
+        EventLog.writeEvent(
+                ANSWER_TAG,
+                String.format(
+                        "avc: %s { %s } scontext=u:r:%s:s0 tcontext=u:object_r:%s:s0 tclass=%s",
+                        granted, permissions, sType, tType, tClass));
+    }
+
+    private static void writeTestLog(
+            String granted,
+            String permissions,
+            String sType,
+            String tType,
+            String tClass,
+            String path) {
+        EventLog.writeEvent(
+                ANSWER_TAG,
+                String.format(
+                        "avc: %s { %s } path=\"%s\" scontext=u:r:%s:s0 tcontext=u:object_r:%s:s0"
+                                + " tclass=%s",
+                        granted, permissions, path, sType, tType, tClass));
+    }
+
+    private static void writeTestLog(
+            String granted,
+            String permissions,
+            String sType,
+            int[] sCategories,
+            String tType,
+            int[] tCategories,
+            String tClass) {
+        EventLog.writeEvent(
+                ANSWER_TAG,
+                String.format(
+                        "avc: %s { %s } scontext=u:r:%s:s0%s tcontext=u:object_r:%s:s0%s tclass=%s",
+                        granted,
+                        permissions,
+                        sType,
+                        toCategoriesString(sCategories),
+                        tType,
+                        toCategoriesString(tCategories),
+                        tClass));
+    }
+
+    private static void writeTestLog(
+            String granted,
+            String permissions,
+            String sType,
+            int[] sCategories,
+            String tType,
+            int[] tCategories,
+            String tClass,
+            String path) {
+        EventLog.writeEvent(
+                ANSWER_TAG,
+                String.format(
+                        "avc: %s { %s } path=\"%s\" scontext=u:r:%s:s0%s"
+                                + " tcontext=u:object_r:%s:s0%s tclass=%s",
+                        granted,
+                        permissions,
+                        path,
+                        sType,
+                        toCategoriesString(sCategories),
+                        tType,
+                        toCategoriesString(tCategories),
+                        tClass));
+    }
+
+    private static void writeTestLog(
+            String granted,
+            String permissions,
+            String sType,
+            String tType,
+            String tClass,
+            boolean permissive) {
+        EventLog.writeEvent(
+                ANSWER_TAG,
+                String.format(
+                        "avc: %s { %s } scontext=u:r:%s:s0 tcontext=u:object_r:%s:s0 tclass=%s"
+                                + " permissive=%s",
+                        granted, permissions, sType, tType, tClass, permissive ? "1" : "0"));
+    }
+
+    private static String toCategoriesString(int[] categories) {
+        return (categories == null || categories.length == 0)
+                ? ""
+                : ":c"
+                        + Arrays.stream(categories)
+                                .mapToObj(String::valueOf)
+                                .collect(Collectors.joining(",c"));
+    }
+
+    private static final class MockClock extends Clock {
+
+        public long currentTimeMillis = 0;
+
+        @Override
+        public long currentTimeMillis() {
+            return currentTimeMillis;
+        }
+    }
+}
diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp
index 654d7a8d..f49f638 100644
--- a/services/tests/powerstatstests/Android.bp
+++ b/services/tests/powerstatstests/Android.bp
@@ -44,6 +44,7 @@
         "servicestests-utils",
         "platform-test-annotations",
         "flag-junit",
+        "ravenwood-junit",
     ],
 
     libs: [
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index ca162e0..ba2b538 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -32,6 +32,7 @@
 import android.os.HandlerThread;
 import android.os.UidBatteryConsumer;
 import android.os.UserBatteryConsumer;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.SparseArray;
 
 import androidx.test.InstrumentationRegistry;
@@ -57,7 +58,8 @@
 
     private final PowerProfile mPowerProfile;
     private final MockClock mMockClock = new MockClock();
-    private final MockBatteryStatsImpl mBatteryStats;
+    private final File mHistoryDir;
+    private MockBatteryStatsImpl mBatteryStats;
     private Handler mHandler;
 
     private BatteryUsageStats mBatteryUsageStats;
@@ -66,6 +68,10 @@
     private SparseArray<int[]> mCpusByPolicy = new SparseArray<>();
     private SparseArray<int[]> mFreqsByPolicy = new SparseArray<>();
 
+    private int mDisplayCount = -1;
+    private int mPerUidModemModel = -1;
+    private NetworkStats mNetworkStats;
+
     public BatteryUsageStatsRule() {
         this(0, null);
     }
@@ -78,16 +84,38 @@
         mHandler = mock(Handler.class);
         mPowerProfile = spy(new PowerProfile());
         mMockClock.currentTime = currentTime;
-        mBatteryStats = new MockBatteryStatsImpl(mMockClock, historyDir, mHandler);
-        mBatteryStats.setPowerProfile(mPowerProfile);
+        mHistoryDir = historyDir;
+
+        if (!RavenwoodRule.isUnderRavenwood()) {
+            lateInitBatteryStats();
+        }
 
         mCpusByPolicy.put(0, new int[]{0, 1, 2, 3});
         mCpusByPolicy.put(4, new int[]{4, 5, 6, 7});
         mFreqsByPolicy.put(0, new int[]{300000, 1000000, 2000000});
         mFreqsByPolicy.put(4, new int[]{300000, 1000000, 2500000, 3000000});
+    }
+
+    private void lateInitBatteryStats() {
+        if (mBatteryStats != null) return;
+
+        mBatteryStats = new MockBatteryStatsImpl(mMockClock, mHistoryDir, mHandler);
+        mBatteryStats.setPowerProfile(mPowerProfile);
         mBatteryStats.setCpuScalingPolicies(new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy));
 
         mBatteryStats.onSystemReady();
+
+        if (mDisplayCount != -1) {
+            mBatteryStats.setDisplayCountLocked(mDisplayCount);
+        }
+        if (mPerUidModemModel != -1) {
+            synchronized (mBatteryStats) {
+                mBatteryStats.setPerUidModemModel(mPerUidModemModel);
+            }
+        }
+        if (mNetworkStats != null) {
+            mBatteryStats.setNetworkStats(mNetworkStats);
+        }
     }
 
     public MockClock getMockClock() {
@@ -112,7 +140,10 @@
         }
         mCpusByPolicy.put(policy, relatedCpus);
         mFreqsByPolicy.put(policy, frequencies);
-        mBatteryStats.setCpuScalingPolicies(new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy));
+        if (mBatteryStats != null) {
+            mBatteryStats.setCpuScalingPolicies(
+                    new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy));
+        }
         return this;
     }
 
@@ -174,13 +205,19 @@
 
     public BatteryUsageStatsRule setNumDisplays(int value) {
         when(mPowerProfile.getNumDisplays()).thenReturn(value);
-        mBatteryStats.setDisplayCountLocked(value);
+        mDisplayCount = value;
+        if (mBatteryStats != null) {
+            mBatteryStats.setDisplayCountLocked(mDisplayCount);
+        }
         return this;
     }
 
     public BatteryUsageStatsRule setPerUidModemModel(int perUidModemModel) {
-        synchronized (mBatteryStats) {
-            mBatteryStats.setPerUidModemModel(perUidModemModel);
+        mPerUidModemModel = perUidModemModel;
+        if (mBatteryStats != null) {
+            synchronized (mBatteryStats) {
+                mBatteryStats.setPerUidModemModel(mPerUidModemModel);
+            }
         }
         return this;
     }
@@ -210,7 +247,10 @@
     }
 
     public void setNetworkStats(NetworkStats networkStats) {
-        mBatteryStats.setNetworkStats(networkStats);
+        mNetworkStats = networkStats;
+        if (mBatteryStats != null) {
+            mBatteryStats.setNetworkStats(mNetworkStats);
+        }
     }
 
     @Override
@@ -225,6 +265,7 @@
     }
 
     private void before() {
+        lateInitBatteryStats();
         HandlerThread bgThread = new HandlerThread("bg thread");
         bgThread.start();
         mHandler = new Handler(bgThread.getLooper());
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index be68e9c..8958fac 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -31,6 +31,10 @@
 
         "test-apps/SuspendTestApp/src/**/*.java",
     ],
+
+    kotlincflags: [
+        "-Werror",
+    ],
     static_libs: [
         "frameworks-base-testutils",
         "services.accessibility",
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index 88b2ed4..071db68 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.biometrics;
 
+import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
 import static android.Manifest.permission.MANAGE_BIOMETRIC;
 import static android.Manifest.permission.TEST_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
@@ -491,6 +492,22 @@
     }
 
     @Test
+    public void testRegisterAuthenticationStateListener_callsFaceService() throws Exception {
+        mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
+        setInternalAndTestBiometricPermissions(mContext, true /* hasPermission */);
+
+        mAuthService = new AuthService(mContext, mInjector);
+        mAuthService.onStart();
+
+        final AuthenticationStateListener listener = mock(AuthenticationStateListener.class);
+
+        mAuthService.mImpl.registerAuthenticationStateListener(listener);
+
+        waitForIdle();
+        verify(mFaceService).registerAuthenticationStateListener(eq(listener));
+    }
+
+    @Test
     public void testRegisterKeyguardCallback_callsBiometricServiceRegisterKeyguardCallback()
             throws Exception {
         setInternalAndTestBiometricPermissions(mContext, true /* hasPermission */);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
index 3a3dd6e..f8b5b04 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.biometrics.sensors.face.aidl;
 
+import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
 import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT;
 import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
@@ -49,6 +50,7 @@
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.TestableContext;
 
 import androidx.test.filters.SmallTest;
@@ -58,6 +60,7 @@
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.log.OperationContextExt;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.LockoutTracker;
@@ -81,6 +84,8 @@
 @SmallTest
 public class FaceAuthenticationClientTest {
 
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private static final int USER_ID = 12;
     private static final long OP_ID = 32;
     private static final int WAKE_REASON = WakeReason.LIFT;
@@ -105,6 +110,8 @@
     @Mock
     private ClientMonitorCallback mCallback;
     @Mock
+    private AuthenticationStateListeners mAuthenticationStateListeners;
+    @Mock
     private AidlResponseHandler mAidlResponseHandler;
     @Mock
     private ActivityTaskManager mActivityTaskManager;
@@ -264,6 +271,29 @@
         verify(mHal, never()).authenticate(anyInt());
     }
 
+    @Test
+    public void testAuthenticationStateListeners_onAuthenticationSucceeded()
+            throws RemoteException {
+        mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
+        final FaceAuthenticationClient client = createClient();
+        client.start(mCallback);
+        client.onAuthenticated(new Face("friendly", 1 /* faceId */, 2 /* deviceId */),
+                true /* authenticated */, new ArrayList<>());
+
+        verify(mAuthenticationStateListeners).onAuthenticationSucceeded(anyInt(), anyInt());
+    }
+
+    @Test
+    public void testAuthenticationStateListeners_onAuthenticationFailed() throws RemoteException {
+        mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
+        final FaceAuthenticationClient client = createClient();
+        client.start(mCallback);
+        client.onAuthenticated(new Face("friendly", 1 /* faceId */, 2 /* deviceId */),
+                false /* authenticated */, new ArrayList<>());
+
+        verify(mAuthenticationStateListeners).onAuthenticationFailed(anyInt(), anyInt());
+    }
+
     private FaceAuthenticationClient createClient() throws RemoteException {
         return createClient(2 /* version */, mClientMonitorCallbackConverter,
                 false /* allowBackgroundAuthentication */,
@@ -311,7 +341,8 @@
                 false /* requireConfirmation */,
                 mBiometricLogger, mBiometricContext, true /* isStrongBiometric */,
                 mUsageStats, lockoutTracker, allowBackgroundAuthentication,
-                null /* sensorPrivacyManager */, 0 /* biometricStrength */) {
+                null /* sensorPrivacyManager */, 0 /* biometricStrength */,
+                mAuthenticationStateListeners) {
             @Override
             protected ActivityTaskManager getActivityTaskManager() {
                 return mActivityTaskManager;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index 772ec8b..7648bd17 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -51,6 +51,7 @@
 import com.android.internal.R;
 import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
@@ -89,6 +90,8 @@
     private BiometricContext mBiometricContext;
     @Mock
     private BiometricStateCallback mBiometricStateCallback;
+    @Mock
+    private AuthenticationStateListeners mAuthenticationStateListeners;
 
     private final TestLooper mLooper = new TestLooper();
     private SensorProps[] mSensorProps;
@@ -119,8 +122,8 @@
         mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
 
         mFaceProvider = new FaceProvider(mContext, mBiometricStateCallback,
-                mSensorProps, TAG, mLockoutResetDispatcher, mBiometricContext,
-                mDaemon, new Handler(mLooper.getLooper()),
+                mAuthenticationStateListeners, mSensorProps, TAG, mLockoutResetDispatcher,
+                mBiometricContext, mDaemon, new Handler(mLooper.getLooper()),
                 false /* resetLockoutRequiresChallenge */, false /* testHalEnabled */);
     }
 
@@ -154,7 +157,7 @@
         final HidlFaceSensorConfig[] hidlFaceSensorConfig =
                 new HidlFaceSensorConfig[]{faceSensorConfig};
         mFaceProvider = new FaceProvider(mContext,
-                mBiometricStateCallback, hidlFaceSensorConfig, TAG,
+                mBiometricStateCallback, mAuthenticationStateListeners, hidlFaceSensorConfig, TAG,
                 mLockoutResetDispatcher, mBiometricContext, mDaemon,
                 new Handler(mLooper.getLooper()),
                 true /* resetLockoutRequiresChallenge */,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
index e558c4d..78c1e08 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
@@ -44,6 +44,7 @@
 
 import com.android.internal.R;
 import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -81,6 +82,8 @@
     private BiometricContext mBiometricContext;
     @Mock
     private BiometricStateCallback mBiometricStateCallback;
+    @Mock
+    private AuthenticationStateListeners mAuthenticationStateListeners;
 
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -116,8 +119,8 @@
 
         Face10.sSystemClock = Clock.fixed(
                 Instant.ofEpochMilli(100), ZoneId.of("America/Los_Angeles"));
-        mFace10 = new Face10(mContext, mBiometricStateCallback, sensorProps,
-                mLockoutResetDispatcher, mHandler, mScheduler, mBiometricContext);
+        mFace10 = new Face10(mContext, mBiometricStateCallback, mAuthenticationStateListeners,
+                sensorProps, mLockoutResetDispatcher, mHandler, mScheduler, mBiometricContext);
         mBinder = new Binder();
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 774ea5b..4ed6f74 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.biometrics.sensors.fingerprint.aidl;
 
+import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
 
 import static com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR;
@@ -451,6 +452,29 @@
     }
 
     @Test
+    public void testAuthenticationStateListeners_onAuthenticationSucceeded()
+            throws RemoteException {
+        mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
+        final FingerprintAuthenticationClient client = createClient();
+        client.start(mCallback);
+        client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */,
+                2 /* deviceId */), true /* authenticated */, new ArrayList<>());
+
+        verify(mAuthenticationStateListeners).onAuthenticationSucceeded(anyInt(), anyInt());
+    }
+
+    @Test
+    public void testAuthenticationStateListeners_onAuthenticationFailed() throws RemoteException {
+        mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
+        final FingerprintAuthenticationClient client = createClient();
+        client.start(mCallback);
+        client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */,
+                2 /* deviceId */), false /* authenticated */, new ArrayList<>());
+
+        verify(mAuthenticationStateListeners).onAuthenticationFailed(anyInt(), anyInt());
+    }
+
+    @Test
     public void cancelsAuthWhenNotInForeground() throws Exception {
         final ActivityManager.RunningTaskInfo topTask = new ActivityManager.RunningTaskInfo();
         topTask.topActivity = new ComponentName("other", "thing");
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
index ccbbaa5..5943832 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
@@ -33,19 +33,21 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
-import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.WindowManager;
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.input.flags.Flags;
 import com.android.server.LocalServices;
 import com.android.server.input.InputManagerInternal;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -58,6 +60,9 @@
     private static final String LANGUAGE_TAG = "en-US";
     private static final String LAYOUT_TYPE = "qwerty";
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Mock
     private InputManagerInternal mInputManagerInternalMock;
     @Mock
@@ -72,11 +77,12 @@
 
     @Before
     public void setUp() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER);
+
         MockitoAnnotations.initMocks(this);
         mInputManagerMockHelper = new InputManagerMockHelper(
                 TestableLooper.get(this), mNativeWrapperMock, mIInputManagerMock);
 
-        doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
         LocalServices.removeServiceForTest(InputManagerInternal.class);
         LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
 
@@ -129,11 +135,7 @@
         mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
                 /* displayId= */ 1);
         verify(mNativeWrapperMock).openUinputMouse(eq("name"), eq(1), eq(1), anyString());
-        verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
-        doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
         mInputController.unregisterInputDevice(deviceToken);
-        verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(
-                eq(Display.INVALID_DISPLAY));
     }
 
     @Test
@@ -143,14 +145,11 @@
         mInputController.createMouse("mouse1", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
                 /* displayId= */ 1);
         verify(mNativeWrapperMock).openUinputMouse(eq("mouse1"), eq(1), eq(1), anyString());
-        verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
         final IBinder deviceToken2 = new Binder();
         mInputController.createMouse("mouse2", /*vendorId= */ 1, /*productId= */ 1, deviceToken2,
                 /* displayId= */ 2);
         verify(mNativeWrapperMock).openUinputMouse(eq("mouse2"), eq(1), eq(1), anyString());
-        verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(2));
         mInputController.unregisterInputDevice(deviceToken);
-        verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 9ff29d2..5442af8 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -339,8 +339,8 @@
         LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
 
         mSetFlagsRule.initAllFlagsToReleaseConfigDefault();
+        mSetFlagsRule.enableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER);
 
-        doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
         doNothing().when(mInputManagerInternalMock)
                 .setMousePointerAccelerationEnabled(anyBoolean(), anyInt());
         doNothing().when(mInputManagerInternalMock).setPointerIconVisible(anyBoolean(), anyInt());
@@ -1333,7 +1333,6 @@
         mInputController.addDeviceForTesting(BINDER, fd,
                 InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS,
                 DEVICE_NAME_1, INPUT_DEVICE_ID);
-        doReturn(DISPLAY_ID_1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
         assertThat(mDeviceImpl.sendButtonEvent(BINDER,
                 new VirtualMouseButtonEvent.Builder()
                         .setButtonCode(buttonCode)
@@ -1363,7 +1362,6 @@
         mInputController.addDeviceForTesting(BINDER, fd,
                 InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS, DEVICE_NAME_1,
                 INPUT_DEVICE_ID);
-        doReturn(DISPLAY_ID_1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
         assertThat(mDeviceImpl.sendRelativeEvent(BINDER,
                 new VirtualMouseRelativeEvent.Builder()
                         .setRelativeX(x)
@@ -1394,7 +1392,6 @@
         mInputController.addDeviceForTesting(BINDER, fd,
                 InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS, DEVICE_NAME_1,
                 INPUT_DEVICE_ID);
-        doReturn(DISPLAY_ID_1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
         assertThat(mDeviceImpl.sendScrollEvent(BINDER,
                 new VirtualMouseScrollEvent.Builder()
                         .setXAxisMovement(x)
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
index 3e4f1df..81981e6 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
@@ -183,9 +183,8 @@
     private VirtualCameraConfig createVirtualCameraConfig(
             int width, int height, int format, int maximumFramesPerSecond,
             String name, int sensorOrientation, int lensFacing) {
-        return new VirtualCameraConfig.Builder()
+        return new VirtualCameraConfig.Builder(name)
                 .addStreamConfig(width, height, format, maximumFramesPerSecond)
-                .setName(name)
                 .setVirtualCameraCallback(mCallbackHandler, mVirtualCameraCallbackMock)
                 .setSensorOrientation(sensorOrientation)
                 .setLensFacing(lensFacing)
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
index 10f27ca..72fa949 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
@@ -80,7 +80,7 @@
         @BeforeClass
         @JvmStatic
         fun checkAllCasesUniquelyNamed() {
-            val duplicateCaseNames = CASES.mapIndexed { caseIndex, testCase ->
+            val duplicateCaseNames = CASES.mapIndexed { _, testCase ->
                 testCase.failures.map {
                     makeTestName(testCase, it.first, Params.Type.FAILURE)
                 } + testCase.allowed.map {
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
index dc1d2c5..1c6d36b 100644
--- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -17,16 +17,19 @@
 package com.android.server.os;
 
 import android.app.admin.flags.Flags;
-import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
 
 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
 
 import android.app.role.RoleManager;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.Binder;
 import android.os.BugreportManager.BugreportCallback;
 import android.os.IBinder;
@@ -48,6 +51,8 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.io.FileDescriptor;
 import java.util.concurrent.CompletableFuture;
@@ -66,6 +71,9 @@
     private BugreportManagerServiceImpl mService;
     private BugreportManagerServiceImpl.BugreportFileManager mBugreportFileManager;
 
+    @Mock
+    private PackageManager mPackageManager;
+
     private int mCallingUid = 1234;
     private String mCallingPackage  = "test.package";
     private AtomicFile mMappingFile;
@@ -74,7 +82,8 @@
     private String mBugreportFile2 = "bugreport-file2.zip";
 
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
         mContext = InstrumentationRegistry.getInstrumentation().getContext();
         mMappingFile = new AtomicFile(mContext.getFilesDir(), "bugreport-mapping.xml");
         ArraySet<String> mAllowlistedPackages = new ArraySet<>();
@@ -83,6 +92,7 @@
                 new BugreportManagerServiceImpl.Injector(mContext, mAllowlistedPackages,
                         mMappingFile));
         mBugreportFileManager = new BugreportManagerServiceImpl.BugreportFileManager(mMappingFile);
+        when(mPackageManager.getPackageUidAsUser(anyString(), anyInt())).thenReturn(mCallingUid);
     }
 
     @After
@@ -115,12 +125,13 @@
 
         assertThrows(IllegalArgumentException.class, () ->
                 mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
-                        mContext, callingInfo, Process.myUserHandle().getIdentifier(),
-                        "unknown-file.zip", /* forceUpdateMapping= */ true));
+                        mContext, mPackageManager,  callingInfo,
+                        Process.myUserHandle().getIdentifier(), "unknown-file.zip",
+                        /* forceUpdateMapping= */ true));
 
         // No exception should be thrown.
         mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
-                mContext, callingInfo, mContext.getUserId(), mBugreportFile,
+                mContext, mPackageManager, callingInfo, mContext.getUserId(), mBugreportFile,
                 /* forceUpdateMapping= */ true);
     }
 
@@ -132,7 +143,7 @@
                 callingInfo, mBugreportFile, /* keepOnRetrieval= */ true);
 
         mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
-                mContext, callingInfo, mContext.getUserId(), mBugreportFile,
+                mContext, mPackageManager, callingInfo, mContext.getUserId(), mBugreportFile,
                 /* forceUpdateMapping= */ true);
 
         assertThat(mBugreportFileManager.mBugreportFilesToPersist).containsExactly(mBugreportFile);
@@ -148,10 +159,10 @@
 
         // No exception should be thrown.
         mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
-                mContext, callingInfo, mContext.getUserId(), mBugreportFile,
+                mContext, mPackageManager, callingInfo, mContext.getUserId(), mBugreportFile,
                 /* forceUpdateMapping= */ true);
         mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
-                mContext, callingInfo, mContext.getUserId(), mBugreportFile2,
+                mContext, mPackageManager, callingInfo, mContext.getUserId(), mBugreportFile2,
                 /* forceUpdateMapping= */ true);
     }
 
@@ -160,8 +171,9 @@
         Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
         assertThrows(IllegalArgumentException.class,
                 () -> mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
-                        mContext, callingInfo, Process.myUserHandle().getIdentifier(),
-                        "test-file.zip", /* forceUpdateMapping= */ true));
+                        mContext, mPackageManager, callingInfo,
+                        Process.myUserHandle().getIdentifier(), "test-file.zip",
+                        /* forceUpdateMapping= */ true));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index a743fff..06be456 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 import static org.testng.Assert.assertEquals;
@@ -33,6 +34,7 @@
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
+import android.os.PersistableBundle;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.platform.test.annotations.Postsubmit;
@@ -1632,6 +1634,106 @@
         assertThat(mainUserCount).isEqualTo(1);
     }
 
+    @Test
+    public void testAddUserAccountData_validStringValuesAreSaved_validBundleIsSaved() {
+        assumeManagedUsersSupported();
+
+        String userName = "User";
+        String accountName = "accountName";
+        String accountType = "accountType";
+        String arrayKey = "StringArrayKey";
+        String stringKey = "StringKey";
+        String intKey = "IntKey";
+        String nestedBundleKey = "PersistableBundleKey";
+        String value1 = "Value 1";
+        String value2 = "Value 2";
+        String value3 = "Value 3";
+
+        UserInfo userInfo = mUserManager.createUser(userName,
+                UserManager.USER_TYPE_FULL_SECONDARY, 0);
+
+        PersistableBundle accountOptions = new PersistableBundle();
+        String[] stringArray = {value1, value2};
+        accountOptions.putInt(intKey, 1234);
+        PersistableBundle nested = new PersistableBundle();
+        nested.putString(stringKey, value3);
+        accountOptions.putPersistableBundle(nestedBundleKey, nested);
+        accountOptions.putStringArray(arrayKey, stringArray);
+
+        mUserManager.clearSeedAccountData();
+        mUserManager.setSeedAccountData(mContext.getUserId(), accountName,
+                accountType, accountOptions);
+
+        //assert userName accountName and accountType were saved correctly
+        assertTrue(mUserManager.getUserInfo(userInfo.id).name.equals(userName));
+        assertTrue(mUserManager.getSeedAccountName().equals(accountName));
+        assertTrue(mUserManager.getSeedAccountType().equals(accountType));
+
+        //assert bundle with correct values was added
+        assertThat(mUserManager.getSeedAccountOptions().containsKey(arrayKey)).isTrue();
+        assertThat(mUserManager.getSeedAccountOptions().getPersistableBundle(nestedBundleKey)
+                .getString(stringKey)).isEqualTo(value3);
+        assertThat(mUserManager.getSeedAccountOptions().getStringArray(arrayKey)[0])
+                .isEqualTo(value1);
+
+        mUserManager.removeUser(userInfo.id);
+    }
+
+    @Test
+    public void testAddUserAccountData_invalidStringValuesAreTruncated_invalidBundleIsDropped() {
+        assumeManagedUsersSupported();
+
+        String tooLongString = generateLongString();
+        String userName = "User " + tooLongString;
+        String accountType = "Account Type " + tooLongString;
+        String accountName = "accountName " + tooLongString;
+        String arrayKey = "StringArrayKey";
+        String stringKey = "StringKey";
+        String intKey = "IntKey";
+        String nestedBundleKey = "PersistableBundleKey";
+        String value1 = "Value 1";
+        String value2 = "Value 2";
+
+        UserInfo userInfo = mUserManager.createUser(userName,
+                UserManager.USER_TYPE_FULL_SECONDARY, 0);
+
+        PersistableBundle accountOptions = new PersistableBundle();
+        String[] stringArray = {value1, value2};
+        accountOptions.putInt(intKey, 1234);
+        PersistableBundle nested = new PersistableBundle();
+        nested.putString(stringKey, tooLongString);
+        accountOptions.putPersistableBundle(nestedBundleKey, nested);
+        accountOptions.putStringArray(arrayKey, stringArray);
+        mUserManager.clearSeedAccountData();
+        mUserManager.setSeedAccountData(mContext.getUserId(), accountName,
+                accountType, accountOptions);
+
+        //assert userName was truncated
+        assertTrue(mUserManager.getUserInfo(userInfo.id).name.length()
+                == UserManager.MAX_USER_NAME_LENGTH);
+
+        //assert accountName and accountType got truncated
+        assertTrue(mUserManager.getSeedAccountName().length()
+                == UserManager.MAX_ACCOUNT_STRING_LENGTH);
+        assertTrue(mUserManager.getSeedAccountType().length()
+                == UserManager.MAX_ACCOUNT_STRING_LENGTH);
+
+        //assert bundle with invalid values was dropped
+        assertThat(mUserManager.getSeedAccountOptions() == null).isTrue();
+
+        mUserManager.removeUser(userInfo.id);
+    }
+
+    private String generateLongString() {
+        String partialString = "Test Name Test Name Test Name Test Name Test Name Test Name Test "
+                + "Name Test Name Test Name Test Name "; //String of length 100
+        StringBuilder resultString = new StringBuilder();
+        for (int i = 0; i < 600; i++) {
+            resultString.append(partialString);
+        }
+        return resultString.toString();
+    }
+
     private boolean isPackageInstalledForUser(String packageName, int userId) {
         try {
             return mPackageManager.getPackageInfoAsUser(packageName, 0, userId) != null;
diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigNamedActorTest.kt b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigNamedActorTest.kt
index 150822b..c07c4d7 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigNamedActorTest.kt
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigNamedActorTest.kt
@@ -18,12 +18,13 @@
 
 import android.content.Context
 import android.util.Xml
-import androidx.test.InstrumentationRegistry
+import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.SystemConfig
 import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertThrows
 import org.junit.Rule
 import org.junit.Test
-import org.junit.rules.ExpectedException
 import org.junit.rules.TemporaryFolder
 
 class SystemConfigNamedActorTest {
@@ -37,14 +38,11 @@
         private const val PACKAGE_TWO = "com.test.actor.two"
     }
 
-    private val context: Context = InstrumentationRegistry.getContext()
+    private val context: Context = InstrumentationRegistry.getInstrumentation().context
 
     @get:Rule
     val tempFolder = TemporaryFolder(context.filesDir)
 
-    @get:Rule
-    val expected = ExpectedException.none()
-
     private var uniqueCounter = 0
 
     @Test
@@ -193,11 +191,9 @@
             </config>
         """.write()
 
-        expected.expect(IllegalStateException::class.java)
-        expected.expectMessage("Defining $ACTOR_ONE as $PACKAGE_ONE " +
+        val exc = assertThrows(IllegalStateException::class.java) { assertPermissions() }
+        assertEquals(exc.message, "Defining $ACTOR_ONE as $PACKAGE_ONE " +
                 "for the android namespace is not allowed")
-
-        assertPermissions()
     }
 
     @Test
@@ -217,11 +213,9 @@
             </config>
         """.write()
 
-        expected.expect(IllegalStateException::class.java)
-        expected.expectMessage("Duplicate actor definition for $NAMESPACE_TEST/$ACTOR_ONE;" +
+        val exc = assertThrows(IllegalStateException::class.java) { assertPermissions() }
+        assertEquals(exc.message, "Duplicate actor definition for $NAMESPACE_TEST/$ACTOR_ONE;" +
                 " defined as both $PACKAGE_ONE and $PACKAGE_TWO")
-
-        assertPermissions()
     }
 
     private fun String.write() = tempFolder.root.resolve("${uniqueCounter++}.xml")
@@ -230,5 +224,5 @@
     private fun assertPermissions() = SystemConfig(false).apply {
         val parser = Xml.newPullParser()
         readPermissions(parser, tempFolder.root, 0)
-    }. let { assertThat(it.namedActors) }
+    }.let { assertThat(it.namedActors) }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
index 0e20daf..99d5a6d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
@@ -143,12 +143,13 @@
                 Policy.policyState(false, true), 0);
 
         ZenPolicy zenPolicy = notificationPolicyToZenPolicy(policy);
-        assertThat(zenPolicy.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_ALLOW);
+        assertThat(zenPolicy.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_ALLOW);
 
         Policy notAllowed = new Policy(0, 0, 0, 0,
                 Policy.policyState(false, false), 0);
         ZenPolicy zenPolicyNotAllowed = notificationPolicyToZenPolicy(notAllowed);
-        assertThat(zenPolicyNotAllowed.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_DISALLOW);
+        assertThat(zenPolicyNotAllowed.getPriorityChannelsAllowed()).isEqualTo(
+                ZenPolicy.STATE_DISALLOW);
     }
 
     @Test
@@ -158,11 +159,12 @@
                 Policy.policyState(false, true), 0);
 
         ZenPolicy zenPolicy = notificationPolicyToZenPolicy(policy);
-        assertThat(zenPolicy.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_UNSET);
+        assertThat(zenPolicy.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_UNSET);
 
         Policy notAllowed = new Policy(0, 0, 0, 0,
                 Policy.policyState(false, false), 0);
         ZenPolicy zenPolicyNotAllowed = notificationPolicyToZenPolicy(notAllowed);
-        assertThat(zenPolicyNotAllowed.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_UNSET);
+        assertThat(zenPolicyNotAllowed.getPriorityChannelsAllowed()).isEqualTo(
+                ZenPolicy.STATE_UNSET);
     }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
index e523e79f..539bb37 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -284,7 +284,7 @@
                 actual.getPriorityConversationSenders());
         assertEquals(expected.getPriorityCallSenders(), actual.getPriorityCallSenders());
         assertEquals(expected.getPriorityMessageSenders(), actual.getPriorityMessageSenders());
-        assertEquals(expected.getPriorityChannels(), actual.getPriorityChannels());
+        assertEquals(expected.getPriorityChannelsAllowed(), actual.getPriorityChannelsAllowed());
     }
 
     @Test
@@ -716,7 +716,7 @@
         assertEquals(policy.getPriorityCategorySystem(), fromXml.getPriorityCategorySystem());
         assertEquals(policy.getPriorityCategoryReminders(), fromXml.getPriorityCategoryReminders());
         assertEquals(policy.getPriorityCategoryEvents(), fromXml.getPriorityCategoryEvents());
-        assertEquals(policy.getPriorityChannels(), fromXml.getPriorityChannels());
+        assertEquals(policy.getPriorityChannelsAllowed(), fromXml.getPriorityChannelsAllowed());
 
         assertEquals(policy.getVisualEffectFullScreenIntent(),
                 fromXml.getVisualEffectFullScreenIntent());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 25ad7db..227265a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -4075,7 +4075,8 @@
 
         // UPDATE_ORIGIN_USER should change the bitmask and change the values.
         assertThat(rule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_PRIORITY);
-        assertThat(rule.getZenPolicy().getPriorityChannels()).isEqualTo(ZenPolicy.STATE_DISALLOW);
+        assertThat(rule.getZenPolicy().getPriorityChannelsAllowed()).isEqualTo(
+                ZenPolicy.STATE_DISALLOW);
 
         assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
 
@@ -4339,7 +4340,8 @@
 
         // New ZenPolicy differs from the default config
         assertThat(rule.getZenPolicy()).isNotNull();
-        assertThat(rule.getZenPolicy().getPriorityChannels()).isEqualTo(ZenPolicy.STATE_DISALLOW);
+        assertThat(rule.getZenPolicy().getPriorityChannelsAllowed()).isEqualTo(
+                ZenPolicy.STATE_DISALLOW);
 
         ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
         assertThat(storedRule.canBeUpdatedByApp()).isFalse();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
index 57e1132..3a88294 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
@@ -218,7 +218,7 @@
 
         // unset applied, channels setting keeps its state
         channelsPriority.apply(unset);
-        assertThat(channelsPriority.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_ALLOW);
+        assertThat(channelsPriority.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_ALLOW);
     }
 
     @Test
@@ -234,7 +234,7 @@
 
         // priority channels (less strict state) cannot override a setting that sets it to none
         none.apply(priority);
-        assertThat(none.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_DISALLOW);
+        assertThat(none.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_DISALLOW);
     }
 
     @Test
@@ -250,7 +250,7 @@
 
         // applying a policy with channelType=none overrides priority setting
         priority.apply(none);
-        assertThat(priority.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_DISALLOW);
+        assertThat(priority.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_DISALLOW);
     }
 
     @Test
@@ -265,7 +265,7 @@
 
         // applying a policy with a set channel type actually goes through
         unset.apply(priority);
-        assertThat(unset.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_ALLOW);
+        assertThat(unset.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_ALLOW);
     }
 
     @Test
@@ -379,7 +379,7 @@
         ZenPolicy.Builder builder = new ZenPolicy.Builder();
 
         ZenPolicy policy = builder.build();
-        assertThat(policy.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_UNSET);
+        assertThat(policy.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_UNSET);
     }
 
     @Test
@@ -696,7 +696,7 @@
         builder.allowPriorityChannels(true);
         ZenPolicy policy = builder.build();
 
-        assertThat(policy.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_UNSET);
+        assertThat(policy.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_UNSET);
         assertThat(policy.toString().contains("allowChannels")).isFalse();
     }
 
@@ -708,12 +708,12 @@
         ZenPolicy.Builder builder = new ZenPolicy.Builder();
         builder.allowPriorityChannels(true);
         ZenPolicy policy = builder.build();
-        assertThat(policy.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_ALLOW);
+        assertThat(policy.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_ALLOW);
 
         // disallow priority channels
         builder.allowPriorityChannels(false);
         policy = builder.build();
-        assertThat(policy.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_DISALLOW);
+        assertThat(policy.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_DISALLOW);
     }
 
     @Test
@@ -734,7 +734,7 @@
         assertThat(newPolicy.getVisualEffectBadge()).isEqualTo(ZenPolicy.STATE_DISALLOW);
         assertThat(newPolicy.getVisualEffectPeek()).isEqualTo(ZenPolicy.STATE_UNSET);
 
-        assertThat(newPolicy.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_ALLOW);
+        assertThat(newPolicy.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_ALLOW);
     }
 
     @Test
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 7c2f7ee..c8abd8d 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -166,6 +166,7 @@
 
     @Mock
     private PhoneWindowManager.ButtonOverridePermissionChecker mButtonOverridePermissionChecker;
+    @Mock private WindowWakeUpPolicy mWindowWakeUpPolicy;
 
     @Mock private IBinder mInputToken;
     @Mock private IBinder mImeTargetWindowToken;
@@ -230,6 +231,10 @@
         TalkbackShortcutController getTalkbackShortcutController() {
             return new TestTalkbackShortcutController(mContext);
         }
+
+        WindowWakeUpPolicy getWindowWakeUpPolicy() {
+            return mWindowWakeUpPolicy;
+        }
     }
 
     TestPhoneWindowManager(Context context, boolean supportSettingsUpdate) {
@@ -620,7 +625,8 @@
 
     void assertPowerWakeUp() {
         mTestLooper.dispatchAll();
-        verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString());
+        verify(mWindowWakeUpPolicy)
+                .wakeUpFromKey(anyLong(), eq(KeyEvent.KEYCODE_POWER), anyBoolean());
     }
 
     void assertNoPowerSleep() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 2a89b02..31d6fa3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -3722,6 +3722,68 @@
         assertFalse(ar.moveFocusableActivityToTop("test"));
     }
 
+    @Test
+    public void testPauseConfigDispatch() throws RemoteException {
+        final Task task = new TaskBuilder(mSupervisor)
+                .setDisplay(mDisplayContent).setCreateActivity(true).build();
+        final ActivityRecord activity = task.getTopNonFinishingActivity();
+        final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
+                TYPE_BASE_APPLICATION);
+        attrs.setTitle("AppWindow");
+        final TestWindowState appWindow = createWindowState(attrs, activity);
+        activity.addWindow(appWindow);
+
+        clearInvocations(mClientLifecycleManager);
+        clearInvocations(activity);
+
+        Configuration ro = activity.getRequestedOverrideConfiguration();
+        ro.windowConfiguration.setBounds(new Rect(20, 0, 120, 200));
+        activity.onRequestedOverrideConfigurationChanged(ro);
+        activity.ensureActivityConfiguration();
+        mWm.mRoot.performSurfacePlacement();
+
+        // policy will center the bounds, so just check for matching size here.
+        assertEquals(100, activity.getWindowConfiguration().getBounds().width());
+        assertEquals(100, appWindow.getWindowConfiguration().getBounds().width());
+        // No scheduled transactions since it asked for a restart.
+        verify(mClientLifecycleManager, times(1)).scheduleTransaction(any());
+        verify(activity, times(1)).setLastReportedConfiguration(any(), any());
+        assertTrue(appWindow.mResizeReported);
+
+        // act like everything drew and went idle
+        appWindow.mResizeReported = false;
+        makeLastConfigReportedToClient(appWindow, true);
+
+        // Now pause dispatch and try to resize
+        activity.pauseConfigurationDispatch();
+
+        ro.windowConfiguration.setBounds(new Rect(20, 0, 150, 200));
+        activity.onRequestedOverrideConfigurationChanged(ro);
+        activity.ensureActivityConfiguration();
+        mWm.mRoot.performSurfacePlacement();
+
+        // Activity should get new config (core-side)
+        assertEquals(130, activity.getWindowConfiguration().getBounds().width());
+        // But windows should not get new config.
+        assertEquals(100, appWindow.getWindowConfiguration().getBounds().width());
+        // The client shouldn't receive any changes
+        verify(mClientLifecycleManager, times(1)).scheduleTransaction(any());
+        // and lastReported shouldn't be set.
+        verify(activity, times(1)).setLastReportedConfiguration(any(), any());
+        // There should be no resize reported to client.
+        assertFalse(appWindow.mResizeReported);
+
+        // Now resume dispatch
+        activity.resumeConfigurationDispatch();
+        mWm.mRoot.performSurfacePlacement();
+
+        // Windows and client should now receive updates
+        verify(activity, times(2)).setLastReportedConfiguration(any(), any());
+        verify(mClientLifecycleManager, times(2)).scheduleTransaction(any());
+        assertEquals(130, appWindow.getWindowConfiguration().getBounds().width());
+        assertTrue(appWindow.mResizeReported);
+    }
+
     private ICompatCameraControlCallback getCompatCameraControlCallback() {
         return new ICompatCameraControlCallback.Stub() {
             @Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 752dc5e8..0c1a9c3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -886,8 +886,6 @@
 
         spyOn(mActivity.mLetterboxUiController);
         doReturn(true).when(mActivity.mLetterboxUiController)
-                .isSurfaceReadyToShow(any());
-        doReturn(true).when(mActivity.mLetterboxUiController)
                 .isSurfaceVisible(any());
 
         assertTrue(mActivity.mLetterboxUiController.shouldShowLetterboxUi(
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 51df1d4..7d8eb90 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1602,6 +1602,33 @@
     }
 
     @Test
+    public void testTransientWithParallelLaunch() {
+        final Task recentTask = mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask();
+        final ActivityRecord recent = new ActivityBuilder(mAtm).setTask(recentTask)
+                .setVisible(false).build();
+        final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final Task appTask = app.getTask();
+        registerTestTransitionPlayer();
+        final TransitionController controller = mRootWindowContainer.mTransitionController;
+        final Transition transition = createTestTransition(TRANSIT_OPEN, controller);
+        transition.mParallelCollectType = Transition.PARALLEL_TYPE_RECENTS;
+        controller.moveToCollecting(transition);
+        transition.collect(recentTask);
+        transition.collect(appTask);
+        transition.setTransientLaunch(recent, appTask);
+        recentTask.moveToFront("move-recent-to-front");
+        transition.setAllReady();
+        transition.start();
+        // Assume that the app starts another activity in its task.
+        final Transition newTransition = controller.createAndStartCollecting(TRANSIT_OPEN);
+
+        assertEquals(newTransition, controller.getCollectingTransition());
+        assertTrue(controller.mWaitingTransitions.contains(transition));
+        assertTrue(controller.isTransientHide(appTask));
+        assertTrue(controller.isTransientVisible(appTask));
+    }
+
+    @Test
     public void testNotReadyPushPop() {
         final TransitionController controller = new TestTransitionController(mAtm);
         controller.setSyncEngine(mWm.mSyncEngine);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index fe9d837..06afa38 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -1068,7 +1068,7 @@
             invocationOnMock.callRealMethod();
             return null;
         }).when(surface).lockCanvas(any());
-        mWm.mAccessibilityController.drawMagnifiedRegionBorderIfNeeded(displayId, mTransaction);
+        mWm.mAccessibilityController.drawMagnifiedRegionBorderIfNeeded(displayId);
         waitUntilHandlersIdle();
         try {
             verify(surface).lockCanvas(any());
@@ -1076,14 +1076,9 @@
             clearInvocations(surface);
             // Invalidate and redraw.
             mWm.mAccessibilityController.onDisplaySizeChanged(mDisplayContent);
-            mWm.mAccessibilityController.drawMagnifiedRegionBorderIfNeeded(displayId, mTransaction);
+            mWm.mAccessibilityController.drawMagnifiedRegionBorderIfNeeded(displayId);
             // Turn off magnification to release surface.
             mWm.mAccessibilityController.setMagnificationCallbacks(displayId, null);
-            if (!com.android.window.flags.Flags.drawMagnifierBorderOutsideWmlock()) {
-                verify(surface).release();
-                assertTrue(lockCanvasInWmLock[0]);
-                return;
-            }
             waitUntilHandlersIdle();
             // lockCanvas must not be called after releasing.
             verify(surface, never()).lockCanvas(any());
diff --git a/telecomm/java/android/telecom/CallControl.java b/telecomm/java/android/telecom/CallControl.java
index fe699af..a1407869 100644
--- a/telecomm/java/android/telecom/CallControl.java
+++ b/telecomm/java/android/telecom/CallControl.java
@@ -21,7 +21,6 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.os.Binder;
 import android.os.Bundle;
@@ -31,7 +30,6 @@
 import android.os.ResultReceiver;
 import android.text.TextUtils;
 
-import com.android.internal.telecom.ClientTransactionalServiceRepository;
 import com.android.internal.telecom.ICallControl;
 import com.android.server.telecom.flags.Flags;
 
@@ -52,20 +50,13 @@
 @SuppressLint("NotCloseable")
 public final class CallControl {
     private static final String TAG = CallControl.class.getSimpleName();
-    private static final String INTERFACE_ERROR_MSG = "Call Control is not available";
     private final String mCallId;
     private final ICallControl mServerInterface;
-    private final PhoneAccountHandle mPhoneAccountHandle;
-    private final ClientTransactionalServiceRepository mRepository;
 
     /** @hide */
-    public CallControl(@NonNull String callId, @Nullable ICallControl serverInterface,
-            @NonNull ClientTransactionalServiceRepository repository,
-            @NonNull PhoneAccountHandle pah) {
+    public CallControl(@NonNull String callId, @NonNull ICallControl serverInterface) {
         mCallId = callId;
         mServerInterface = serverInterface;
-        mRepository = repository;
-        mPhoneAccountHandle = pah;
     }
 
     /**
@@ -97,16 +88,14 @@
      */
     public void setActive(@CallbackExecutor @NonNull Executor executor,
             @NonNull OutcomeReceiver<Void, CallException> callback) {
-        if (mServerInterface != null) {
-            try {
-                mServerInterface.setActive(mCallId,
-                        new CallControlResultReceiver("setActive", executor, callback));
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+        try {
+            mServerInterface.setActive(mCallId,
+                    new CallControlResultReceiver("setActive", executor, callback));
 
-            } catch (RemoteException e) {
-                throw e.rethrowAsRuntimeException();
-            }
-        } else {
-            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
         }
     }
 
@@ -134,16 +123,12 @@
         validateVideoState(videoState);
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
-        if (mServerInterface != null) {
-            try {
-                mServerInterface.answer(videoState, mCallId,
-                        new CallControlResultReceiver("answer", executor, callback));
+        try {
+            mServerInterface.answer(videoState, mCallId,
+                    new CallControlResultReceiver("answer", executor, callback));
 
-            } catch (RemoteException e) {
-                throw e.rethrowAsRuntimeException();
-            }
-        } else {
-            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
         }
     }
 
@@ -165,16 +150,14 @@
      */
     public void setInactive(@CallbackExecutor @NonNull Executor executor,
             @NonNull OutcomeReceiver<Void, CallException> callback) {
-        if (mServerInterface != null) {
-            try {
-                mServerInterface.setInactive(mCallId,
-                        new CallControlResultReceiver("setInactive", executor, callback));
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+        try {
+            mServerInterface.setInactive(mCallId,
+                    new CallControlResultReceiver("setInactive", executor, callback));
 
-            } catch (RemoteException e) {
-                throw e.rethrowAsRuntimeException();
-            }
-        } else {
-            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
         }
     }
 
@@ -213,15 +196,11 @@
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
         validateDisconnectCause(disconnectCause);
-        if (mServerInterface != null) {
-            try {
-                mServerInterface.disconnect(mCallId, disconnectCause,
-                        new CallControlResultReceiver("disconnect", executor, callback));
-            } catch (RemoteException e) {
-                throw e.rethrowAsRuntimeException();
-            }
-        } else {
-            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        try {
+            mServerInterface.disconnect(mCallId, disconnectCause,
+                    new CallControlResultReceiver("disconnect", executor, callback));
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
         }
     }
 
@@ -245,15 +224,13 @@
      */
     public void startCallStreaming(@CallbackExecutor @NonNull Executor executor,
             @NonNull OutcomeReceiver<Void, CallException> callback) {
-        if (mServerInterface != null) {
-            try {
-                mServerInterface.startCallStreaming(mCallId,
-                        new CallControlResultReceiver("startCallStreaming", executor, callback));
-            } catch (RemoteException e) {
-                throw e.rethrowAsRuntimeException();
-            }
-        } else {
-            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+        try {
+            mServerInterface.startCallStreaming(mCallId,
+                    new CallControlResultReceiver("startCallStreaming", executor, callback));
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
         }
     }
 
@@ -281,15 +258,11 @@
         Objects.requireNonNull(callEndpoint);
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
-        if (mServerInterface != null) {
-            try {
-                mServerInterface.requestCallEndpointChange(callEndpoint,
-                        new CallControlResultReceiver("endpointChange", executor, callback));
-            } catch (RemoteException e) {
-                throw e.rethrowAsRuntimeException();
-            }
-        } else {
-            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        try {
+            mServerInterface.requestCallEndpointChange(callEndpoint,
+                    new CallControlResultReceiver("requestCallEndpointChange", executor, callback));
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
         }
     }
 
@@ -313,20 +286,16 @@
      *                 passed that details why the operation failed.
      */
     @FlaggedApi(Flags.FLAG_SET_MUTE_STATE)
-    public void setMuteState(boolean isMuted, @CallbackExecutor @NonNull Executor executor,
+    public void requestMuteState(boolean isMuted, @CallbackExecutor @NonNull Executor executor,
             @NonNull OutcomeReceiver<Void, CallException> callback) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
-        if (mServerInterface != null) {
-            try {
-                mServerInterface.setMuteState(isMuted,
-                        new CallControlResultReceiver("setMuteState", executor, callback));
+        try {
+            mServerInterface.setMuteState(isMuted,
+                    new CallControlResultReceiver("requestMuteState", executor, callback));
 
-            } catch (RemoteException e) {
-                throw e.rethrowAsRuntimeException();
-            }
-        } else {
-            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
         }
     }
 
@@ -352,14 +321,10 @@
     public void sendEvent(@NonNull String event, @NonNull Bundle extras) {
         Objects.requireNonNull(event);
         Objects.requireNonNull(extras);
-        if (mServerInterface != null) {
-            try {
-                mServerInterface.sendEvent(mCallId, event, extras);
-            } catch (RemoteException e) {
-                throw e.rethrowAsRuntimeException();
-            }
-        } else {
-            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        try {
+            mServerInterface.sendEvent(mCallId, event, extras);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
         }
     }
 
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index a089f5c..63db297 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -580,6 +580,9 @@
             mExtras = phoneAccount.getExtras();
             mGroupId = phoneAccount.getGroupId();
             mSupportedAudioRoutes = phoneAccount.getSupportedAudioRoutes();
+            if (phoneAccount.hasSimultaneousCallingRestriction()) {
+                mSimultaneousCallingRestriction = phoneAccount.getSimultaneousCallingRestriction();
+            }
         }
 
         /**
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index e81f482..2c6e1e4 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1360,7 +1360,7 @@
     /**
      * Returns a list of {@link PhoneAccountHandle}s which can be used to make and receive phone
      * calls. The returned list includes those accounts which have been explicitly enabled by
-     * the user or other users visible to the user.
+     * the user or enabled by other users but visible to the user.
      *
      * @see #EXTRA_PHONE_ACCOUNT_HANDLE
      * @return A list of {@code PhoneAccountHandle} objects.
@@ -1486,7 +1486,7 @@
             return service.getCallCapablePhoneAccounts(includeDisabledAccounts,
                     mContext.getOpPackageName(), mContext.getAttributionTag(), true).getList();
         } catch (RemoteException e) {
-            throw e.rethrowAsRuntimeException();
+            throw e.rethrowFromSystemServer();
         }
     }
 
diff --git a/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
index 71e9184..467e89c 100644
--- a/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
+++ b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
@@ -208,8 +208,7 @@
                 if (resultCode == TELECOM_TRANSACTION_SUCCESS) {
 
                     // create the interface object that the client will interact with
-                    CallControl control = new CallControl(callId, callControl, mRepository,
-                            mPhoneAccountHandle);
+                    CallControl control = new CallControl(callId, callControl);
                     // give the client the object via the OR that was passed into addCall
                     pendingControl.onResult(control);
 
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 1badf67..a73c46b 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9430,16 +9430,6 @@
             "missed_incoming_call_sms_originator_string_array";
 
     /**
-     * String array of Apn Type configurations.
-     * The entries should be of form "APN_TYPE_NAME:priority".
-     * priority is an integer that is sorted from highest to lowest.
-     * example: cbs:5
-     *
-     * @hide
-     */
-    public static final String KEY_APN_PRIORITY_STRING_ARRAY = "apn_priority_string_array";
-
-    /**
      * Network capability priority for determine the satisfy order in telephony. The priority is
      * from the lowest 0 to the highest 100. The long-lived network shall have the lowest priority.
      * This allows other short-lived requests like MMS requests to be established. Emergency request
@@ -10755,17 +10745,14 @@
                 TimeUnit.DAYS.toMillis(1));
         sDefaults.putStringArray(KEY_MISSED_INCOMING_CALL_SMS_ORIGINATOR_STRING_ARRAY,
                 new String[0]);
-        sDefaults.putStringArray(KEY_APN_PRIORITY_STRING_ARRAY, new String[] {
-                "enterprise:0", "default:1", "mms:2", "supl:2", "dun:2", "hipri:3", "fota:2",
-                "ims:2", "cbs:2", "ia:2", "emergency:2", "mcx:3", "xcap:3"
-        });
 
         // Do not modify the priority unless you know what you are doing. This will have significant
         // impacts on the order of data network setup.
         sDefaults.putStringArray(
                 KEY_TELEPHONY_NETWORK_CAPABILITY_PRIORITIES_STRING_ARRAY, new String[] {
                         "eims:90", "supl:80", "mms:70", "xcap:70", "cbs:50", "mcx:50", "fota:50",
-                        "ims:40", "dun:30", "enterprise:20", "internet:20"
+                        "ims:40", "rcs:40", "dun:30", "enterprise:20", "internet:20",
+                        "prioritize_bandwidth:20", "prioritize_latency:20"
                 });
         sDefaults.putStringArray(
                 KEY_TELEPHONY_DATA_SETUP_RETRY_RULES_STRING_ARRAY, new String[] {
@@ -10777,9 +10764,10 @@
                         // registration state changes) retry can still happen.
                         "permanent_fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|"
                                 + "-3|65543|65547|2252|2253|2254, retry_interval=2500",
-                        "capabilities=mms|supl|cbs, retry_interval=2000",
-                        "capabilities=internet|enterprise|dun|ims|fota, retry_interval=2500|3000|"
-                                + "5000|10000|15000|20000|40000|60000|120000|240000|"
+                        "capabilities=mms|supl|cbs|rcs, retry_interval=2000",
+                        "capabilities=internet|enterprise|dun|ims|fota|xcap|mcx|"
+                                + "prioritize_bandwidth|prioritize_latency, retry_interval="
+                                + "2500|3000|5000|10000|15000|20000|40000|60000|120000|240000|"
                                 + "600000|1200000|1800000, maximum_retries=20"
                 });
         sDefaults.putStringArray(
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index cbd5524..c1ceaef 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -25,6 +25,7 @@
 import android.Manifest;
 import android.annotation.BytesLong;
 import android.annotation.CallbackExecutor;
+import android.annotation.CurrentTimeMillisLong;
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.LongDef;
@@ -18714,51 +18715,93 @@
      * call diagnostic data
      * @hide
      */
-    public static class EmergencyCallDiagnosticParams {
+    @SystemApi
+    @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+    public static final class EmergencyCallDiagnosticParams {
+        public static final class Builder {
+            private boolean mCollectTelecomDumpSys;
+            private boolean mCollectTelephonyDumpsys;
 
-       private boolean mCollectTelecomDumpSys;
-       private boolean mCollectTelephonyDumpsys;
-       private boolean mCollectLogcat;
+            // If this is set to a value other than -1L, then the logcat collection is enabled.
+            // Logcat lines with this time or greater are collected how much is collected is
+            // dependent on internal implementation. Time represented as milliseconds since boot.
+            private long mLogcatStartTimeMillis = sUnsetLogcatStartTime;
 
-        //logcat lines with this time or greater are collected
-        //how much is collected is dependent on internal implementation.
-        //Time represented as milliseconds since January 1, 1970 UTC
+            /**
+             * Allows enabling of telecom dumpsys collection.
+             * @param collectTelecomDumpsys Determines whether telecom dumpsys should be collected.
+             * @return Builder instance corresponding to the configured call diagnostic params.
+             */
+            public @NonNull Builder setTelecomDumpSysCollectionEnabled(
+                    boolean collectTelecomDumpsys) {
+                mCollectTelecomDumpSys = collectTelecomDumpsys;
+                return this;
+            }
+
+            /**
+             * Allows enabling of telephony dumpsys collection.
+             * @param collectTelephonyDumpsys Determines if telephony dumpsys should be collected.
+             * @return Builder instance corresponding to the configured call diagnostic params.
+             */
+            public @NonNull Builder setTelephonyDumpSysCollectionEnabled(
+                    boolean collectTelephonyDumpsys) {
+                mCollectTelephonyDumpsys = collectTelephonyDumpsys;
+                return this;
+            }
+
+            /**
+             * Allows enabling of logcat (system,radio) collection.
+             * @param startTimeMillis Enables logcat collection as of the indicated timestamp.
+             * @return Builder instance corresponding to the configured call diagnostic params.
+             */
+            public @NonNull Builder setLogcatCollectionStartTimeMillis(
+                    @CurrentTimeMillisLong long startTimeMillis) {
+                mLogcatStartTimeMillis = startTimeMillis;
+                return this;
+            }
+
+            /**
+             * Build the EmergencyCallDiagnosticParams from the provided Builder config.
+             * @return {@link EmergencyCallDiagnosticParams} instance from provided builder.
+             */
+            public @NonNull EmergencyCallDiagnosticParams build() {
+                return new EmergencyCallDiagnosticParams(mCollectTelecomDumpSys,
+                        mCollectTelephonyDumpsys, mLogcatStartTimeMillis);
+            }
+        }
+
+        private boolean mCollectTelecomDumpSys;
+        private boolean mCollectTelephonyDumpsys;
+        private boolean mCollectLogcat;
         private long mLogcatStartTimeMillis;
 
+        private static long sUnsetLogcatStartTime = -1L;
+
+        private EmergencyCallDiagnosticParams(boolean collectTelecomDumpSys,
+                boolean collectTelephonyDumpsys, long logcatStartTimeMillis) {
+            mCollectTelecomDumpSys = collectTelecomDumpSys;
+            mCollectTelephonyDumpsys = collectTelephonyDumpsys;
+            mLogcatStartTimeMillis = logcatStartTimeMillis;
+            mCollectLogcat = logcatStartTimeMillis != sUnsetLogcatStartTime;
+        }
 
         public boolean isTelecomDumpSysCollectionEnabled() {
             return mCollectTelecomDumpSys;
         }
 
-        public void setTelecomDumpSysCollection(boolean collectTelecomDumpSys) {
-            mCollectTelecomDumpSys = collectTelecomDumpSys;
-        }
-
         public boolean isTelephonyDumpSysCollectionEnabled() {
             return mCollectTelephonyDumpsys;
         }
 
-        public void setTelephonyDumpSysCollection(boolean collectTelephonyDumpsys) {
-            mCollectTelephonyDumpsys = collectTelephonyDumpsys;
-        }
-
         public boolean isLogcatCollectionEnabled() {
             return mCollectLogcat;
         }
 
-        public long getLogcatStartTime()
+        public long getLogcatCollectionStartTimeMillis()
         {
             return mLogcatStartTimeMillis;
         }
 
-        public void setLogcatCollection(boolean collectLogcat, long startTimeMillis) {
-            mCollectLogcat = collectLogcat;
-            if(mCollectLogcat)
-            {
-                mLogcatStartTimeMillis = startTimeMillis;
-            }
-        }
-
         @Override
         public String toString() {
             return "EmergencyCallDiagnosticParams{" +
@@ -18774,11 +18817,12 @@
      * Request telephony to persist state for debugging emergency call failures.
      *
      * @param dropboxTag Tag to use when persisting data to dropbox service.
-     *
-     * @see params Parameters controlling what is collected
+     * @param params Parameters controlling what is collected.
      *
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
     @RequiresPermission(android.Manifest.permission.DUMP)
     public void persistEmergencyCallDiagnosticData(@NonNull String dropboxTag,
             @NonNull EmergencyCallDiagnosticParams params) {
@@ -18791,7 +18835,7 @@
             if (telephony != null) {
                 telephony.persistEmergencyCallDiagnosticData(dropboxTag,
                         params.isLogcatCollectionEnabled(),
-                        params.getLogcatStartTime(),
+                        params.getLogcatCollectionStartTimeMillis(),
                         params.isTelecomDumpSysCollectionEnabled(),
                         params.isTelephonyDumpSysCollectionEnabled());
             }
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 70047a6..a1ac477 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -34,7 +34,6 @@
 import android.os.OutcomeReceiver;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
-import android.os.ServiceSpecificException;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyFrameworkInitializer;
@@ -336,6 +335,12 @@
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_MODEM_BUSY = 22;
 
+    /**
+     * Telephony process is not currently available or satellite is not supported.
+     */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public static final int SATELLITE_RESULT_ILLEGAL_STATE = 23;
+
     /** @hide */
     @IntDef(prefix = {"SATELLITE_RESULT_"}, value = {
             SATELLITE_RESULT_SUCCESS,
@@ -360,7 +365,8 @@
             SATELLITE_RESULT_NOT_AUTHORIZED,
             SATELLITE_RESULT_NOT_SUPPORTED,
             SATELLITE_RESULT_REQUEST_IN_PROGRESS,
-            SATELLITE_RESULT_MODEM_BUSY
+            SATELLITE_RESULT_MODEM_BUSY,
+            SATELLITE_RESULT_ILLEGAL_STATE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface SatelliteResult {}
@@ -510,7 +516,7 @@
             }
         } catch (RemoteException ex) {
             Rlog.e(TAG, "requestSatelliteEnabled() RemoteException: ", ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -526,7 +532,6 @@
      *                 will return a {@link SatelliteException} with the {@link SatelliteResult}.
      *
      * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -561,11 +566,12 @@
                 };
                 telephony.requestIsSatelliteEnabled(mSubId, receiver);
             } else {
-                throw new IllegalStateException("telephony service is null.");
+                executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                        new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
             }
         } catch (RemoteException ex) {
             loge("requestIsSatelliteEnabled() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -581,7 +587,6 @@
      *                 will return a {@link SatelliteException} with the {@link SatelliteResult}.
      *
      * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -616,11 +621,12 @@
                 };
                 telephony.requestIsDemoModeEnabled(mSubId, receiver);
             } else {
-                throw new IllegalStateException("telephony service is null.");
+                executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                        new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
             }
         } catch (RemoteException ex) {
             loge("requestIsDemoModeEnabled() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -639,8 +645,6 @@
      *                 service is supported on the device and {@code false} otherwise.
      *                 If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
      *                 will return a {@link SatelliteException} with the {@link SatelliteResult}.
-     *
-     * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void requestIsSatelliteSupported(@NonNull @CallbackExecutor Executor executor,
@@ -674,11 +678,12 @@
                 };
                 telephony.requestIsSatelliteSupported(mSubId, receiver);
             } else {
-                throw new IllegalStateException("telephony service is null.");
+                executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                        new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
             }
         } catch (RemoteException ex) {
             loge("requestIsSatelliteSupported() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -693,7 +698,6 @@
      *                 will return a {@link SatelliteException} with the {@link SatelliteResult}.
      *
      * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -729,11 +733,12 @@
                 };
                 telephony.requestSatelliteCapabilities(mSubId, receiver);
             } else {
-                throw new IllegalStateException("telephony service is null.");
+                executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                        new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
             }
         } catch (RemoteException ex) {
             loge("requestSatelliteCapabilities() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -959,7 +964,6 @@
      * @param callback The callback to notify of satellite transmission updates.
      *
      * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1009,11 +1013,12 @@
                 telephony.startSatelliteTransmissionUpdates(mSubId, errorCallback,
                         internalCallback);
             } else {
-                throw new IllegalStateException("telephony service is null.");
+                executor.execute(() -> Binder.withCleanCallingIdentity(
+                        () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
             }
         } catch (RemoteException ex) {
             loge("startSatelliteTransmissionUpdates() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -1029,7 +1034,6 @@
      * @param resultListener Listener for the {@link SatelliteResult} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1063,11 +1067,12 @@
                             () -> resultListener.accept(SATELLITE_RESULT_INVALID_ARGUMENTS)));
                 }
             } else {
-                throw new IllegalStateException("telephony service is null.");
+                executor.execute(() -> Binder.withCleanCallingIdentity(
+                        () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
             }
         } catch (RemoteException ex) {
             loge("stopSatelliteTransmissionUpdates() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -1112,11 +1117,12 @@
                 cancelRemote = telephony.provisionSatelliteService(mSubId, token, provisionData,
                         errorCallback);
             } else {
-                throw new IllegalStateException("telephony service is null.");
+                executor.execute(() -> Binder.withCleanCallingIdentity(
+                        () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
             }
         } catch (RemoteException ex) {
             loge("provisionSatelliteService() RemoteException=" + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
         if (cancellationSignal != null) {
             cancellationSignal.setRemote(cancelRemote);
@@ -1138,7 +1144,6 @@
      * @param resultListener Listener for the {@link SatelliteResult} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1161,11 +1166,12 @@
                 };
                 telephony.deprovisionSatelliteService(mSubId, token, errorCallback);
             } else {
-                throw new IllegalStateException("telephony service is null.");
+                executor.execute(() -> Binder.withCleanCallingIdentity(
+                        () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
             }
         } catch (RemoteException ex) {
             loge("deprovisionSatelliteService() RemoteException=" + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -1208,7 +1214,7 @@
             }
         } catch (RemoteException ex) {
             loge("registerForSatelliteProvisionStateChanged() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
         return SATELLITE_RESULT_REQUEST_FAILED;
     }
@@ -1244,7 +1250,7 @@
             }
         } catch (RemoteException ex) {
             loge("unregisterForSatelliteProvisionStateChanged() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -1260,7 +1266,6 @@
      *                 will return a {@link SatelliteException} with the {@link SatelliteResult}.
      *
      * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1295,11 +1300,12 @@
                 };
                 telephony.requestIsSatelliteProvisioned(mSubId, receiver);
             } else {
-                throw new IllegalStateException("telephony service is null.");
+                executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                        new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
             }
         } catch (RemoteException ex) {
             loge("requestIsSatelliteProvisioned() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -1340,7 +1346,7 @@
             }
         } catch (RemoteException ex) {
             loge("registerForSatelliteModemStateChanged() RemoteException:" + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
         return SATELLITE_RESULT_REQUEST_FAILED;
     }
@@ -1376,7 +1382,7 @@
             }
         } catch (RemoteException ex) {
             loge("unregisterForSatelliteModemStateChanged() RemoteException:" + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -1436,7 +1442,7 @@
             }
         } catch (RemoteException ex) {
             loge("registerForSatelliteDatagram() RemoteException:" + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
         return SATELLITE_RESULT_REQUEST_FAILED;
     }
@@ -1471,7 +1477,7 @@
             }
         } catch (RemoteException ex) {
             loge("unregisterForSatelliteDatagram() RemoteException:" + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -1488,7 +1494,6 @@
      * @param resultListener Listener for the {@link SatelliteResult} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1509,11 +1514,12 @@
                 };
                 telephony.pollPendingSatelliteDatagrams(mSubId, internalCallback);
             } else {
-                throw new IllegalStateException("telephony service is null.");
+                executor.execute(() -> Binder.withCleanCallingIdentity(
+                        () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
             }
         } catch (RemoteException ex) {
             loge("pollPendingSatelliteDatagrams() RemoteException:" + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -1541,7 +1547,6 @@
      * @param resultListener Listener for the {@link SatelliteResult} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1566,11 +1571,12 @@
                 telephony.sendSatelliteDatagram(mSubId, datagramType, datagram,
                         needFullScreenPointingUI, internalCallback);
             } else {
-                throw new IllegalStateException("telephony service is null.");
+                executor.execute(() -> Binder.withCleanCallingIdentity(
+                        () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
             }
         } catch (RemoteException ex) {
             loge("sendSatelliteDatagram() RemoteException:" + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -1587,7 +1593,6 @@
      *                 will return a {@link SatelliteException} with the {@link SatelliteResult}.
      *
      * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1624,12 +1629,13 @@
                 telephony.requestIsSatelliteCommunicationAllowedForCurrentLocation(mSubId,
                         receiver);
             } else {
-                throw new IllegalStateException("telephony service is null.");
+                executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                        new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
             }
         } catch (RemoteException ex) {
             loge("requestIsSatelliteCommunicationAllowedForCurrentLocation() RemoteException: "
                     + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -1645,7 +1651,6 @@
      *                 will return a {@link SatelliteException} with the {@link SatelliteResult}.
      *
      * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1681,11 +1686,12 @@
                 };
                 telephony.requestTimeForNextSatelliteVisibility(mSubId, receiver);
             } else {
-                throw new IllegalStateException("telephony service is null.");
+                executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                        new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
             }
         } catch (RemoteException ex) {
             loge("requestTimeForNextSatelliteVisibility() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -1713,7 +1719,7 @@
             }
         } catch (RemoteException ex) {
             loge("informDeviceAlignedToSatellite() RemoteException:" + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -1727,7 +1733,7 @@
      * <ul>
      * <li>Users want to enable it.</li>
      * <li>There is no satellite communication restriction, which is added by
-     * {@link #addSatelliteAttachRestrictionForCarrier(int, Executor, Consumer)}</li>
+     * {@link #addSatelliteAttachRestrictionForCarrier(int, int, Executor, Consumer)}</li>
      * <li>The carrier config {@link
      * android.telephony.CarrierConfigManager#KEY_SATELLITE_ATTACH_SUPPORTED_BOOL} is set to
      * {@code true}.</li>
@@ -1739,7 +1745,6 @@
      * @param resultListener Listener for the {@link SatelliteResult} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
      * @throws IllegalArgumentException if the subscription is invalid.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@@ -1799,7 +1804,6 @@
      * @param resultListener Listener for the {@link SatelliteResult} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
      * @throws IllegalArgumentException if the subscription is invalid.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@@ -1824,11 +1828,12 @@
                 };
                 telephony.addSatelliteAttachRestrictionForCarrier(subId, reason, errorCallback);
             } else {
-                throw new IllegalStateException("telephony service is null.");
+                executor.execute(() -> Binder.withCleanCallingIdentity(
+                        () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
             }
         } catch (RemoteException ex) {
             loge("addSatelliteAttachRestrictionForCarrier() RemoteException:" + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -1842,7 +1847,6 @@
      * @param resultListener Listener for the {@link SatelliteResult} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
      * @throws IllegalArgumentException if the subscription is invalid.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@@ -1867,17 +1871,18 @@
                 };
                 telephony.removeSatelliteAttachRestrictionForCarrier(subId, reason, errorCallback);
             } else {
-                throw new IllegalStateException("telephony service is null.");
+                executor.execute(() -> Binder.withCleanCallingIdentity(
+                        () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
             }
         } catch (RemoteException ex) {
             loge("removeSatelliteAttachRestrictionForCarrier() RemoteException:" + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
     /**
      * Get reasons for disallowing satellite attach, as requested by
-     * {@link #addSatelliteAttachRestrictionForCarrier(int, Executor, Consumer)}
+     * {@link #addSatelliteAttachRestrictionForCarrier(int, int, Executor, Consumer)}
      *
      * @param subId The subscription ID of the carrier.
      * @return Set of reasons for disallowing satellite communication.
@@ -1910,7 +1915,7 @@
             }
         } catch (RemoteException ex) {
             loge("getSatelliteAttachRestrictionReasonsForCarrier() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
         return new HashSet<>();
     }
@@ -1932,11 +1937,12 @@
      * The {@link NtnSignalStrength#NTN_SIGNAL_STRENGTH_NONE} will be returned if there is no
      * signal strength data available.
      * If the request is not successful, {@link OutcomeReceiver#onError(Throwable)} will return a
-     * {@link SatelliteException} with the {@link SatelliteResult}.
+     * {@link SatelliteException} with the {@link SatelliteResult}, or return a
+     * {@link IllegalStateException} if the Telephony process is not currently available or
+     * satellite is not supported, or return a {@link RuntimeException} when remote procedure call
+     * has failed.
      *
      * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available or
-     * satellite is not supported.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1972,11 +1978,12 @@
                 };
                 telephony.requestNtnSignalStrength(mSubId, receiver);
             } else {
-                throw new IllegalStateException("Telephony service is null.");
+                executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                        new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
             }
         } catch (RemoteException ex) {
             loge("requestNtnSignalStrength() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -1997,12 +2004,11 @@
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
-     * @throws SatelliteException if the callback registration operation fails.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void registerForNtnSignalStrengthChanged(@NonNull @CallbackExecutor Executor executor,
-            @NonNull NtnSignalStrengthCallback callback) throws SatelliteException {
+            @NonNull NtnSignalStrengthCallback callback) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
 
@@ -2024,12 +2030,9 @@
             } else {
                 throw new IllegalStateException("Telephony service is null.");
             }
-        } catch (ServiceSpecificException ex) {
-            logd("registerForNtnSignalStrengthChanged() registration fails: " + ex.errorCode);
-            throw new SatelliteException(ex.errorCode);
         } catch (RemoteException ex) {
             loge("registerForNtnSignalStrengthChanged() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -2072,7 +2075,7 @@
             }
         } catch (RemoteException ex) {
             loge("unregisterForNtnSignalStrengthChanged() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -2113,7 +2116,7 @@
             }
         } catch (RemoteException ex) {
             loge("registerForSatelliteCapabilitiesChanged() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
         return SATELLITE_RESULT_REQUEST_FAILED;
     }
@@ -2149,7 +2152,7 @@
             }
         } catch (RemoteException ex) {
             loge("unregisterForSatelliteCapabilitiesChanged() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -2177,7 +2180,7 @@
             }
         } catch (RemoteException ex) {
             loge("getAllSatellitePlmnsForCarrier() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
         return new ArrayList<>();
     }
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index 256a469..566e51a 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -24,6 +24,7 @@
 import android.hardware.input.InputManagerGlobal
 import android.os.test.TestLooper
 import android.platform.test.annotations.Presubmit
+import android.platform.test.flag.junit.SetFlagsRule
 import android.provider.Settings
 import android.test.mock.MockContentResolver
 import android.view.Display
@@ -72,6 +73,9 @@
     @get:Rule
     val fakeSettingsProviderRule = FakeSettingsProvider.rule()!!
 
+    @get:Rule
+    val setFlagsRule = SetFlagsRule()
+
     @Mock
     private lateinit var native: NativeInputManagerService
 
@@ -170,6 +174,8 @@
 
     @Test
     fun testSetVirtualMousePointerDisplayId() {
+        setFlagsRule.disableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER)
+
         // Set the virtual mouse pointer displayId, and ensure that the calling thread is blocked
         // until the native callback happens.
         var countDownLatch = CountDownLatch(1)
@@ -221,6 +227,8 @@
 
     @Test
     fun testSetVirtualMousePointerDisplayId_unsuccessfulUpdate() {
+        setFlagsRule.disableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER)
+
         // Set the virtual mouse pointer displayId, and ensure that the calling thread is blocked
         // until the native callback happens.
         val countDownLatch = CountDownLatch(1)
@@ -246,6 +254,8 @@
 
     @Test
     fun testSetVirtualMousePointerDisplayId_competingRequests() {
+        setFlagsRule.disableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER)
+
         val firstRequestSyncLatch = CountDownLatch(1)
         doAnswer {
             firstRequestSyncLatch.countDown()
@@ -289,6 +299,8 @@
 
     @Test
     fun onDisplayRemoved_resetAllAdditionalInputProperties() {
+        setFlagsRule.disableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER)
+
         setVirtualMousePointerDisplayIdAndVerify(10)
 
         localService.setPointerIconVisible(false, 10)
@@ -313,6 +325,8 @@
 
     @Test
     fun updateAdditionalInputPropertiesForOverrideDisplay() {
+        setFlagsRule.disableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER)
+
         setVirtualMousePointerDisplayIdAndVerify(10)
 
         localService.setPointerIconVisible(false, 10)
@@ -341,6 +355,8 @@
 
     @Test
     fun setAdditionalInputPropertiesBeforeOverride() {
+        setFlagsRule.disableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER)
+
         localService.setPointerIconVisible(false, 10)
         localService.setMousePointerAccelerationEnabled(false, 10)
 
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java
index 1ec1d5f..2f6a361 100644
--- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java
@@ -15,42 +15,181 @@
  */
 package com.android.hoststubgen.nativesubstitution;
 
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Predicate;
+
 public class SystemProperties_host {
+    private static final Object sLock = new Object();
+
+    /** Active system property values */
+    @GuardedBy("sLock")
+    private static Map<String, String> sValues;
+    /** Predicate tested to determine if a given key can be read. */
+    @GuardedBy("sLock")
+    private static Predicate<String> sKeyReadablePredicate;
+    /** Predicate tested to determine if a given key can be written. */
+    @GuardedBy("sLock")
+    private static Predicate<String> sKeyWritablePredicate;
+    /** Callback to trigger when values are changed */
+    @GuardedBy("sLock")
+    private static Runnable sChangeCallback;
+
+    /**
+     * Reverse mapping that provides a way back to an original key from the
+     * {@link System#identityHashCode(Object)} of {@link String#intern}.
+     */
+    @GuardedBy("sLock")
+    private static SparseArray<String> sKeyHandles = new SparseArray<>();
+
+    public static void native_init$ravenwood(Map<String, String> values,
+            Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate,
+            Runnable changeCallback) {
+        synchronized (sLock) {
+            sValues = Objects.requireNonNull(values);
+            sKeyReadablePredicate = Objects.requireNonNull(keyReadablePredicate);
+            sKeyWritablePredicate = Objects.requireNonNull(keyWritablePredicate);
+            sChangeCallback = Objects.requireNonNull(changeCallback);
+            sKeyHandles.clear();
+        }
+    }
+
+    public static void native_reset$ravenwood() {
+        synchronized (sLock) {
+            sValues = null;
+            sKeyReadablePredicate = null;
+            sKeyWritablePredicate = null;
+            sChangeCallback = null;
+            sKeyHandles.clear();
+        }
+    }
+
+    public static void native_set(String key, String val) {
+        synchronized (sLock) {
+            Objects.requireNonNull(key);
+            Preconditions.requireNonNullViaRavenwoodRule(sValues);
+            if (!sKeyWritablePredicate.test(key)) {
+                throw new IllegalArgumentException(
+                        "Write access to system property '" + key + "' denied via RavenwoodRule");
+            }
+            if (key.startsWith("ro.") && sValues.containsKey(key)) {
+                throw new IllegalArgumentException(
+                        "System property '" + key + "' already defined once; cannot redefine");
+            }
+            if ((val == null) || val.isEmpty()) {
+                sValues.remove(key);
+            } else {
+                sValues.put(key, val);
+            }
+            sChangeCallback.run();
+        }
+    }
+
     public static String native_get(String key, String def) {
-        throw new RuntimeException("Not implemented yet");
+        synchronized (sLock) {
+            Objects.requireNonNull(key);
+            Preconditions.requireNonNullViaRavenwoodRule(sValues);
+            if (!sKeyReadablePredicate.test(key)) {
+                throw new IllegalArgumentException(
+                        "Read access to system property '" + key + "' denied via RavenwoodRule");
+            }
+            return sValues.getOrDefault(key, def);
+        }
     }
+
     public static int native_get_int(String key, int def) {
-        throw new RuntimeException("Not implemented yet");
+        try {
+            return Integer.parseInt(native_get(key, ""));
+        } catch (NumberFormatException ignored) {
+            return def;
+        }
     }
+
     public static long native_get_long(String key, long def) {
-        throw new RuntimeException("Not implemented yet");
+        try {
+            return Long.parseLong(native_get(key, ""));
+        } catch (NumberFormatException ignored) {
+            return def;
+        }
     }
+
     public static boolean native_get_boolean(String key, boolean def) {
-        throw new RuntimeException("Not implemented yet");
+        return parseBoolean(native_get(key, ""), def);
     }
 
     public static long native_find(String name) {
-        throw new RuntimeException("Not implemented yet");
+        synchronized (sLock) {
+            Preconditions.requireNonNullViaRavenwoodRule(sValues);
+            if (sValues.containsKey(name)) {
+                name = name.intern();
+                final int handle = System.identityHashCode(name);
+                sKeyHandles.put(handle, name);
+                return handle;
+            } else {
+                return 0;
+            }
+        }
     }
+
     public static String native_get(long handle) {
-        throw new RuntimeException("Not implemented yet");
+        synchronized (sLock) {
+            return native_get(sKeyHandles.get((int) handle), "");
+        }
     }
+
     public static int native_get_int(long handle, int def) {
-        throw new RuntimeException("Not implemented yet");
+        synchronized (sLock) {
+            return native_get_int(sKeyHandles.get((int) handle), def);
+        }
     }
+
     public static long native_get_long(long handle, long def) {
-        throw new RuntimeException("Not implemented yet");
+        synchronized (sLock) {
+            return native_get_long(sKeyHandles.get((int) handle), def);
+        }
     }
+
     public static boolean native_get_boolean(long handle, boolean def) {
-        throw new RuntimeException("Not implemented yet");
+        synchronized (sLock) {
+            return native_get_boolean(sKeyHandles.get((int) handle), def);
+        }
     }
-    public static void native_set(String key, String def) {
-        throw new RuntimeException("Not implemented yet");
-    }
+
     public static void native_add_change_callback() {
-        throw new RuntimeException("Not implemented yet");
+        // Ignored; callback always registered via init above
     }
+
     public static void native_report_sysprop_change() {
-        throw new RuntimeException("Not implemented yet");
+        // Report through callback always registered via init above
+        synchronized (sLock) {
+            Preconditions.requireNonNullViaRavenwoodRule(sValues);
+            sChangeCallback.run();
+        }
+    }
+
+    private static boolean parseBoolean(String val, boolean def) {
+        // Matches system/libbase/include/android-base/parsebool.h
+        if (val == null) return def;
+        switch (val) {
+            case "1":
+            case "on":
+            case "true":
+            case "y":
+            case "yes":
+                return true;
+            case "0":
+            case "false":
+            case "n":
+            case "no":
+            case "off":
+                return false;
+            default:
+                return def;
+        }
     }
 }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
index 8ca4732..76bac92 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
@@ -24,6 +24,7 @@
         private val classes: ClassNodes,
         val aidlPolicy: FilterPolicyWithReason?,
         val featureFlagsPolicy: FilterPolicyWithReason?,
+        val syspropsPolicy: FilterPolicyWithReason?,
         fallback: OutputFilter
 ) : DelegatingFilter(fallback) {
     override fun getPolicyForClass(className: String): FilterPolicyWithReason {
@@ -33,6 +34,9 @@
         if (featureFlagsPolicy != null && classes.isFeatureFlagsClass(className)) {
             return featureFlagsPolicy
         }
+        if (syspropsPolicy != null && classes.isSyspropsClass(className)) {
+            return syspropsPolicy
+        }
         return super.getPolicyForClass(className)
     }
 }
@@ -57,3 +61,13 @@
             || className.endsWith("/FeatureFlagsImpl")
             || className.endsWith("/FakeFeatureFlagsImpl");
 }
+
+/**
+ * @return if a given class "seems like" a sysprops class.
+ */
+private fun ClassNodes.isSyspropsClass(className: String): Boolean {
+    // Matches template classes defined here:
+    // https://cs.android.com/android/platform/superproject/main/+/main:system/tools/sysprop/
+    return className.startsWith("android/sysprop/")
+            && className.endsWith("Properties")
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
index d38a6e3..7fdd944 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
@@ -64,6 +64,7 @@
 
         var aidlPolicy: FilterPolicyWithReason? = null
         var featureFlagsPolicy: FilterPolicyWithReason? = null
+        var syspropsPolicy: FilterPolicyWithReason? = null
 
         try {
             BufferedReader(FileReader(filename)).use { reader ->
@@ -141,6 +142,14 @@
                                         featureFlagsPolicy =
                                                 policy.withReason("$FILTER_REASON (feature flags)")
                                     }
+                                    SpecialClass.Sysprops -> {
+                                        if (syspropsPolicy != null) {
+                                            throw ParseException(
+                                                    "Policy for sysprops already defined")
+                                        }
+                                        syspropsPolicy =
+                                                policy.withReason("$FILTER_REASON (sysprops)")
+                                    }
                                 }
                             }
                         }
@@ -205,10 +214,10 @@
         }
 
         var ret: OutputFilter = imf
-        if (aidlPolicy != null || featureFlagsPolicy != null) {
+        if (aidlPolicy != null || featureFlagsPolicy != null || syspropsPolicy != null) {
             log.d("AndroidHeuristicsFilter enabled")
             ret = AndroidHeuristicsFilter(
-                    classes, aidlPolicy, featureFlagsPolicy, imf)
+                    classes, aidlPolicy, featureFlagsPolicy, syspropsPolicy, imf)
         }
         return ret
     }
@@ -218,6 +227,7 @@
     NotSpecial,
     Aidl,
     FeatureFlags,
+    Sysprops,
 }
 
 private fun resolveSpecialClass(className: String): SpecialClass {
@@ -227,6 +237,7 @@
     when (className.lowercase()) {
         ":aidl" -> return SpecialClass.Aidl
         ":feature_flags" -> return SpecialClass.FeatureFlags
+        ":sysprops" -> return SpecialClass.Sysprops
     }
     throw ParseException("Invalid special class name \"$className\"")
 }