Merge "Correctly propagate origin to ZenModeEventLogger" into main
diff --git a/Android.bp b/Android.bp
index 96d26cf..bb93048 100644
--- a/Android.bp
+++ b/Android.bp
@@ -97,6 +97,7 @@
         // AIDL sources from external directories
         ":android.hardware.biometrics.common-V4-java-source",
         ":android.hardware.biometrics.fingerprint-V3-java-source",
+        ":android.hardware.biometrics.face-V4-java-source",
         ":android.hardware.gnss-V2-java-source",
         ":android.hardware.graphics.common-V3-java-source",
         ":android.hardware.keymaster-V4-java-source",
@@ -206,11 +207,13 @@
 // framework_srcs. These have no or very limited dependency to the framework.
 java_library {
     name: "framework-internal-utils",
+    defaults: [
+        "android.hardware.power-java_static",
+    ],
     static_libs: [
         "apex_aidl_interface-java",
         "packagemanager_aidl-java",
         "framework-protos",
-        "libtombstone_proto_java",
         "updatable-driver-protos",
         "ota_metadata_proto_java",
         "android.hidl.base-V1.0-java",
diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp
index d03bbd2..e7adf20 100644
--- a/ProtoLibraries.bp
+++ b/ProtoLibraries.bp
@@ -34,6 +34,7 @@
         ":ipconnectivity-proto-src",
         ":libstats_atom_enum_protos",
         ":libstats_atom_message_protos",
+        ":libtombstone_proto-src",
         "core/proto/**/*.proto",
         "libs/incident/**/*.proto",
     ],
diff --git a/STABILITY_OWNERS b/STABILITY_OWNERS
deleted file mode 100644
index a7ecb4d..0000000
--- a/STABILITY_OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-gaillard@google.com
-
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 83db4cb..900c902 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1828,7 +1828,9 @@
                     /* system_measured_source_download_bytes */0,
                     /* system_measured_source_upload_bytes */ 0,
                     /* system_measured_calling_download_bytes */0,
-                    /* system_measured_calling_upload_bytes */ 0);
+                    /* system_measured_calling_upload_bytes */ 0,
+                    jobStatus.getJob().getIntervalMillis(),
+                    jobStatus.getJob().getFlexMillis());
 
             // If the job is immediately ready to run, then we can just immediately
             // put it in the pending list and try to schedule it.  This is especially
@@ -2269,7 +2271,9 @@
                     /* system_measured_source_download_bytes */ 0,
                     /* system_measured_source_upload_bytes */ 0,
                     /* system_measured_calling_download_bytes */0,
-                    /* system_measured_calling_upload_bytes */ 0);
+                    /* system_measured_calling_upload_bytes */ 0,
+                    cancelled.getJob().getIntervalMillis(),
+                    cancelled.getJob().getFlexMillis());
         }
         // If this is a replacement, bring in the new version of the job
         if (incomingJob != null) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 6449edc..3addf9f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -534,7 +534,9 @@
                     /* system_measured_source_download_bytes */ 0,
                     /* system_measured_source_upload_bytes */ 0,
                     /* system_measured_calling_download_bytes */ 0,
-                    /* system_measured_calling_upload_bytes */ 0);
+                    /* system_measured_calling_upload_bytes */ 0,
+                    job.getJob().getIntervalMillis(),
+                    job.getJob().getFlexMillis());
             sEnqueuedJwiAtJobStart.logSampleWithUid(job.getUid(), job.getWorkCount());
             final String sourcePackage = job.getSourcePackageName();
             if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
@@ -1616,7 +1618,9 @@
                 TrafficStats.getUidRxBytes(completedJob.getUid())
                         - mInitialDownloadedBytesFromCalling,
                 TrafficStats.getUidTxBytes(completedJob.getUid())
-                        - mInitialUploadedBytesFromCalling);
+                        - mInitialUploadedBytesFromCalling,
+                completedJob.getJob().getIntervalMillis(),
+                completedJob.getJob().getFlexMillis());
         if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
             Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
                     getId());
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 820d2b0..77b74e9 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -398,8 +398,8 @@
             break;
     }
 
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
 
diff --git a/core/api/current.txt b/core/api/current.txt
index d1d7983..d3ed89b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -13657,6 +13657,16 @@
     field public int uiMode;
   }
 
+  @FlaggedApi("android.content.res.font_scale_converter_public") public interface FontScaleConverter {
+    method public float convertDpToSp(float);
+    method public float convertSpToDp(float);
+  }
+
+  @FlaggedApi("android.content.res.font_scale_converter_public") public class FontScaleConverterFactory {
+    method @FlaggedApi("android.content.res.font_scale_converter_public") @AnyThread @Nullable public static android.content.res.FontScaleConverter forScale(float);
+    method @FlaggedApi("android.content.res.font_scale_converter_public") @AnyThread public static boolean isNonLinearFontScalingActive(float);
+  }
+
   public class ObbInfo implements android.os.Parcelable {
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
@@ -35864,19 +35874,19 @@
     field public static final String NAMESPACE = "data2";
   }
 
-  public static final class ContactsContract.CommonDataKinds.Im implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
-    method public static CharSequence getProtocolLabel(android.content.res.Resources, int, CharSequence);
-    method public static int getProtocolLabelResource(int);
-    method public static CharSequence getTypeLabel(android.content.res.Resources, int, @Nullable CharSequence);
-    method public static int getTypeLabelResource(int);
-    field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/im";
-    field public static final String CUSTOM_PROTOCOL = "data6";
+  @Deprecated public static final class ContactsContract.CommonDataKinds.Im implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
+    method @Deprecated public static CharSequence getProtocolLabel(android.content.res.Resources, int, CharSequence);
+    method @Deprecated public static int getProtocolLabelResource(int);
+    method @Deprecated public static CharSequence getTypeLabel(android.content.res.Resources, int, @Nullable CharSequence);
+    method @Deprecated public static int getTypeLabelResource(int);
+    field @Deprecated public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/im";
+    field @Deprecated public static final String CUSTOM_PROTOCOL = "data6";
     field public static final String EXTRA_ADDRESS_BOOK_INDEX = "android.provider.extra.ADDRESS_BOOK_INDEX";
     field public static final String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "android.provider.extra.ADDRESS_BOOK_INDEX_COUNTS";
     field public static final String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "android.provider.extra.ADDRESS_BOOK_INDEX_TITLES";
-    field public static final String PROTOCOL = "data5";
+    field @Deprecated public static final String PROTOCOL = "data5";
     field @Deprecated public static final int PROTOCOL_AIM = 0; // 0x0
-    field public static final int PROTOCOL_CUSTOM = -1; // 0xffffffff
+    field @Deprecated public static final int PROTOCOL_CUSTOM = -1; // 0xffffffff
     field @Deprecated public static final int PROTOCOL_GOOGLE_TALK = 5; // 0x5
     field @Deprecated public static final int PROTOCOL_ICQ = 6; // 0x6
     field @Deprecated public static final int PROTOCOL_JABBER = 7; // 0x7
@@ -35885,9 +35895,9 @@
     field @Deprecated public static final int PROTOCOL_QQ = 4; // 0x4
     field @Deprecated public static final int PROTOCOL_SKYPE = 3; // 0x3
     field @Deprecated public static final int PROTOCOL_YAHOO = 2; // 0x2
-    field public static final int TYPE_HOME = 1; // 0x1
-    field public static final int TYPE_OTHER = 3; // 0x3
-    field public static final int TYPE_WORK = 2; // 0x2
+    field @Deprecated public static final int TYPE_HOME = 1; // 0x1
+    field @Deprecated public static final int TYPE_OTHER = 3; // 0x3
+    field @Deprecated public static final int TYPE_WORK = 2; // 0x2
   }
 
   public static final class ContactsContract.CommonDataKinds.Nickname implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
@@ -36002,17 +36012,17 @@
     field public static final int TYPE_SPOUSE = 14; // 0xe
   }
 
-  public static final class ContactsContract.CommonDataKinds.SipAddress implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
-    method public static CharSequence getTypeLabel(android.content.res.Resources, int, @Nullable CharSequence);
-    method public static int getTypeLabelResource(int);
-    field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/sip_address";
+  @Deprecated public static final class ContactsContract.CommonDataKinds.SipAddress implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
+    method @Deprecated public static CharSequence getTypeLabel(android.content.res.Resources, int, @Nullable CharSequence);
+    method @Deprecated public static int getTypeLabelResource(int);
+    field @Deprecated public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/sip_address";
     field public static final String EXTRA_ADDRESS_BOOK_INDEX = "android.provider.extra.ADDRESS_BOOK_INDEX";
     field public static final String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "android.provider.extra.ADDRESS_BOOK_INDEX_COUNTS";
     field public static final String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "android.provider.extra.ADDRESS_BOOK_INDEX_TITLES";
-    field public static final String SIP_ADDRESS = "data1";
-    field public static final int TYPE_HOME = 1; // 0x1
-    field public static final int TYPE_OTHER = 3; // 0x3
-    field public static final int TYPE_WORK = 2; // 0x2
+    field @Deprecated public static final String SIP_ADDRESS = "data1";
+    field @Deprecated public static final int TYPE_HOME = 1; // 0x1
+    field @Deprecated public static final int TYPE_OTHER = 3; // 0x3
+    field @Deprecated public static final int TYPE_WORK = 2; // 0x2
   }
 
   public static final class ContactsContract.CommonDataKinds.StructuredName implements android.provider.ContactsContract.DataColumnsWithJoins {
@@ -42791,6 +42801,7 @@
     method @Deprecated @RequiresPermission(android.Manifest.permission.ANSWER_PHONE_CALLS) public boolean endCall();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.net.Uri getAdnUriForPhoneAccount(android.telecom.PhoneAccountHandle);
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getCallCapablePhoneAccounts();
+    method @FlaggedApi("com.android.internal.telephony.flags.work_profile_api_split") @NonNull @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.INTERACT_ACROSS_PROFILES}) public java.util.List<android.telecom.PhoneAccountHandle> getCallCapablePhoneAccountsAcrossProfiles();
     method public String getDefaultDialerPackage();
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telecom.PhoneAccountHandle getDefaultOutgoingPhoneAccount(String);
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_SMS, android.Manifest.permission.READ_PHONE_NUMBERS}, conditional=true) public String getLine1Number(android.telecom.PhoneAccountHandle);
@@ -46612,6 +46623,8 @@
     method public int getTransportType();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field public static final int ATTR_EPDG_OVER_CELL_INTERNET = 1; // 0x1
+    field @FlaggedApi("com.android.internal.telephony.flags.emergency_registration_state") public static final int ATTR_REGISTRATION_TYPE_EMERGENCY = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.emergency_registration_state") public static final int ATTR_VIRTUAL_FOR_ANONYMOUS_EMERGENCY_CALL = 4; // 0x4
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.ImsRegistrationAttributes> CREATOR;
   }
 
@@ -53897,6 +53910,7 @@
     field public static final String PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH";
     field public static final String PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE = "android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";
     field public static final String PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE = "android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE";
+    field @FlaggedApi("com.android.window.flags.app_compat_properties_api") public static final String PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE = "android.window.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE";
     field public static final String PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE = "android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE";
     field public static final String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS = "android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS";
     field public static final String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index fc0fb35..63b1429 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -158,6 +158,7 @@
     field public static final String INTERACT_ACROSS_USERS_FULL = "android.permission.INTERACT_ACROSS_USERS_FULL";
     field public static final String INTERNAL_SYSTEM_WINDOW = "android.permission.INTERNAL_SYSTEM_WINDOW";
     field public static final String INVOKE_CARRIER_SETUP = "android.permission.INVOKE_CARRIER_SETUP";
+    field public static final String KEYPHRASE_ENROLLMENT_APPLICATION = "android.permission.KEYPHRASE_ENROLLMENT_APPLICATION";
     field public static final String KILL_ALL_BACKGROUND_PROCESSES = "android.permission.KILL_ALL_BACKGROUND_PROCESSES";
     field public static final String KILL_UID = "android.permission.KILL_UID";
     field public static final String LAUNCH_DEVICE_MANAGER_SETUP = "android.permission.LAUNCH_DEVICE_MANAGER_SETUP";
@@ -214,6 +215,7 @@
     field public static final String MANAGE_USB = "android.permission.MANAGE_USB";
     field public static final String MANAGE_USERS = "android.permission.MANAGE_USERS";
     field public static final String MANAGE_USER_OEM_UNLOCK_STATE = "android.permission.MANAGE_USER_OEM_UNLOCK_STATE";
+    field public static final String MANAGE_VOICE_KEYPHRASES = "android.permission.MANAGE_VOICE_KEYPHRASES";
     field public static final String MANAGE_WALLPAPER_EFFECTS_GENERATION = "android.permission.MANAGE_WALLPAPER_EFFECTS_GENERATION";
     field public static final String MANAGE_WEAK_ESCROW_TOKEN = "android.permission.MANAGE_WEAK_ESCROW_TOKEN";
     field public static final String MANAGE_WEARABLE_SENSING_SERVICE = "android.permission.MANAGE_WEARABLE_SENSING_SERVICE";
@@ -3348,7 +3350,7 @@
   }
 
   @FlaggedApi("android.companion.virtual.flags.virtual_camera") public interface VirtualCameraCallback {
-    method public void onProcessCaptureRequest(int, long, @Nullable android.companion.virtual.camera.VirtualCameraMetadata);
+    method public default void onProcessCaptureRequest(int, long);
     method public void onStreamClosed(int);
     method public void onStreamConfigured(int, @NonNull android.view.Surface, @NonNull android.companion.virtual.camera.VirtualCameraStreamConfig);
   }
@@ -3369,12 +3371,6 @@
     method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setVirtualCameraCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.camera.VirtualCameraCallback);
   }
 
-  @FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCameraMetadata implements android.os.Parcelable {
-    method public int describeContents();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.camera.VirtualCameraMetadata> CREATOR;
-  }
-
   @FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCameraStreamConfig implements android.os.Parcelable {
     ctor public VirtualCameraStreamConfig(@IntRange(from=1) int, @IntRange(from=1) int, int);
     method public int describeContents();
@@ -8987,9 +8983,9 @@
 package android.media.voice {
 
   public final class KeyphraseModelManager {
-    method @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public void deleteKeyphraseSoundModel(int, @NonNull java.util.Locale);
-    method @Nullable @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel getKeyphraseSoundModel(int, @NonNull java.util.Locale);
-    method @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public void updateKeyphraseSoundModel(@NonNull android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_VOICE_KEYPHRASES) public void deleteKeyphraseSoundModel(int, @NonNull java.util.Locale);
+    method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_VOICE_KEYPHRASES) public android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel getKeyphraseSoundModel(int, @NonNull java.util.Locale);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_VOICE_KEYPHRASES) public void updateKeyphraseSoundModel(@NonNull android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel);
   }
 
 }
@@ -13051,7 +13047,7 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(@NonNull String, @NonNull java.util.Locale, @Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.AlwaysOnHotwordDetector.Callback);
     method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.HotwordDetector createHotwordDetector(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull android.service.voice.HotwordDetector.Callback);
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.HotwordDetector createHotwordDetector(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.HotwordDetector.Callback);
-    method @NonNull @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public final android.media.voice.KeyphraseModelManager createKeyphraseModelManager();
+    method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_VOICE_KEYPHRASES) public final android.media.voice.KeyphraseModelManager createKeyphraseModelManager();
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.VisualQueryDetector createVisualQueryDetector(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.VisualQueryDetector.Callback);
     method @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public void setShouldReceiveSandboxedTrainingData(boolean);
   }
@@ -13514,6 +13510,7 @@
     method public java.util.List<android.telecom.PhoneAccount> getAllPhoneAccounts();
     method public int getAllPhoneAccountsCount();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getCallCapablePhoneAccounts(boolean);
+    method @FlaggedApi("com.android.internal.telephony.flags.work_profile_api_split") @NonNull @RequiresPermission(allOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.INTERACT_ACROSS_PROFILES}) public java.util.List<android.telecom.PhoneAccountHandle> getCallCapablePhoneAccountsAcrossProfiles(boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}, conditional=true) public int getCallState();
     method public android.telecom.PhoneAccountHandle getConnectionManager();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getCurrentTtyMode();
@@ -15723,6 +15720,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAvailable(int, int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isCapable(int, int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void isSupported(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>) throws android.telephony.ims.ImsException;
+    method @FlaggedApi("com.android.internal.telephony.flags.emergency_registration_state") public void registerImsEmergencyRegistrationCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RegistrationManager.RegistrationCallback) throws android.telephony.ims.ImsException;
     method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerImsRegistrationCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.RegistrationCallback) throws android.telephony.ims.ImsException;
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setAdvancedCallingSettingEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCrossSimCallingEnabled(boolean) throws android.telephony.ims.ImsException;
@@ -15733,6 +15731,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setVoWiFiRoamingSettingEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setVoWiFiSettingEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setVtSettingEnabled(boolean);
+    method @FlaggedApi("com.android.internal.telephony.flags.emergency_registration_state") public void unregisterImsEmergencyRegistrationCallback(@NonNull android.telephony.ims.RegistrationManager.RegistrationCallback);
     method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterImsRegistrationCallback(@NonNull android.telephony.ims.ImsMmTelManager.RegistrationCallback);
   }
 
@@ -15765,6 +15764,8 @@
     ctor public ImsRegistrationAttributes.Builder(int);
     method @NonNull public android.telephony.ims.ImsRegistrationAttributes build();
     method @NonNull public android.telephony.ims.ImsRegistrationAttributes.Builder setFeatureTags(@NonNull java.util.Set<java.lang.String>);
+    method @FlaggedApi("com.android.internal.telephony.flags.emergency_registration_state") @NonNull public android.telephony.ims.ImsRegistrationAttributes.Builder setFlagRegistrationTypeEmergency();
+    method @FlaggedApi("com.android.internal.telephony.flags.emergency_registration_state") @NonNull public android.telephony.ims.ImsRegistrationAttributes.Builder setFlagVirtualRegistrationForEmergencyCall();
     method @NonNull public android.telephony.ims.ImsRegistrationAttributes.Builder setSipDetails(@NonNull android.telephony.ims.SipDetails);
   }
 
@@ -16691,6 +16692,7 @@
     ctor public ImsRegistrationImplBase(@NonNull java.util.concurrent.Executor);
     method public final void onDeregistered(android.telephony.ims.ImsReasonInfo);
     method public final void onDeregistered(@Nullable android.telephony.ims.ImsReasonInfo, int, int);
+    method @FlaggedApi("com.android.internal.telephony.flags.emergency_registration_state") public final void onDeregistered(@Nullable android.telephony.ims.ImsReasonInfo, int, @NonNull android.telephony.ims.ImsRegistrationAttributes);
     method public final void onDeregistered(@Nullable android.telephony.ims.ImsReasonInfo, @NonNull android.telephony.ims.SipDetails);
     method public final void onDeregistered(@Nullable android.telephony.ims.ImsReasonInfo, int, int, @NonNull android.telephony.ims.SipDetails);
     method public final void onRegistered(int);
@@ -16699,6 +16701,7 @@
     method public final void onRegistering(@NonNull android.telephony.ims.ImsRegistrationAttributes);
     method public final void onSubscriberAssociatedUriChanged(android.net.Uri[]);
     method public final void onTechnologyChangeFailed(int, android.telephony.ims.ImsReasonInfo);
+    method @FlaggedApi("com.android.internal.telephony.flags.emergency_registration_state") public final void onTechnologyChangeFailed(@Nullable android.telephony.ims.ImsReasonInfo, @NonNull android.telephony.ims.ImsRegistrationAttributes);
     method public void triggerFullNetworkRegistration(@IntRange(from=100, to=699) int, @Nullable String);
     method public void triggerSipDelegateDeregistration();
     method public void updateSipDelegateRegistration();
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index cef11bb..b2a28b2 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -1995,8 +1995,12 @@
     New API must be flagged with @FlaggedApi: field android.Manifest.permission.ALWAYS_UPDATE_WALLPAPER
 UnflaggedApi: android.Manifest.permission#CAMERA_HEADLESS_SYSTEM_USER:
     New API must be flagged with @FlaggedApi: field android.Manifest.permission.CAMERA_HEADLESS_SYSTEM_USER
+UnflaggedApi: android.Manifest.permission#KEYPHRASE_ENROLLMENT_APPLICATION:
+    New API must be flagged with @FlaggedApi: field android.Manifest.permission.KEYPHRASE_ENROLLMENT_APPLICATION
 UnflaggedApi: android.Manifest.permission#LAUNCH_PERMISSION_SETTINGS:
     New API must be flagged with @FlaggedApi: field android.Manifest.permission.LAUNCH_PERMISSION_SETTINGS
+UnflaggedApi: android.Manifest.permission#MANAGE_VOICE_KEYPHRASES:
+    New API must be flagged with @FlaggedApi: field android.Manifest.permission.MANAGE_VOICE_KEYPHRASES
 UnflaggedApi: android.Manifest.permission#READ_INSTALLED_SESSION_PATHS:
     New API must be flagged with @FlaggedApi: field android.Manifest.permission.READ_INSTALLED_SESSION_PATHS
 UnflaggedApi: android.Manifest.permission#REGISTER_NSD_OFFLOAD_ENGINE:
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index e2f4939..aaeba66 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2135,7 +2135,7 @@
 package android.media.voice {
 
   public final class KeyphraseModelManager {
-    method @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public void setModelDatabaseForTestEnabled(boolean);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_VOICE_KEYPHRASES) public void setModelDatabaseForTestEnabled(boolean);
   }
 
 }
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index ea9bb39..37692d3 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -537,8 +537,8 @@
 
     /**
      * Returns whether the given user requires credential entry at this time. This is used to
-     * intercept activity launches for locked work apps due to work challenge being triggered or
-     * when the profile user is yet to be unlocked.
+     * intercept activity launches for apps corresponding to locked profiles due to separate
+     * challenge being triggered or when the profile user is yet to be unlocked.
      */
     public abstract boolean shouldConfirmCredentials(@UserIdInt int userId);
 
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 87c86df..287d2bd 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -2909,7 +2909,7 @@
         try {
             return mPM.setPackagesSuspendedAsUser(packageNames, suspended, appExtras,
                     launcherExtras, dialogInfo, flags, mContext.getOpPackageName(),
-                    getUserId());
+                    UserHandle.myUserId() /* suspendingUserId */, getUserId() /* targetUserId */);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 820ff3e..be420de 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -77,6 +77,7 @@
 import android.os.RemoteException;
 import android.os.StrictMode;
 import android.os.SystemProperties;
+import android.os.Trace;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -91,7 +92,6 @@
 import libcore.io.IoUtils;
 
 import java.io.BufferedInputStream;
-import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -614,11 +614,14 @@
                 ColorManagementProxy cmProxy) {
             if (mService != null) {
                 try {
+                    Trace.beginSection("WPMS.isWallpaperSupported");
                     if (!mService.isWallpaperSupported(context.getOpPackageName())) {
                         return null;
                     }
                 } catch (RemoteException e) {
                     throw e.rethrowFromSystemServer();
+                } finally {
+                    Trace.endSection();
                 }
             }
             synchronized (this) {
@@ -629,6 +632,7 @@
                 mCachedWallpaper = null;
                 Bitmap currentWallpaper = null;
                 try {
+                    Trace.beginSection("WPMS.getCurrentWallpaperLocked");
                     currentWallpaper = getCurrentWallpaperLocked(
                             context, which, userId, hardware, cmProxy);
                 } catch (OutOfMemoryError e) {
@@ -654,6 +658,8 @@
                         // Post-O apps really most sincerely need the permission.
                         throw e;
                     }
+                } finally {
+                    Trace.endSection();
                 }
                 if (currentWallpaper != null) {
                     mCachedWallpaper = new CachedWallpaper(currentWallpaper, userId, which);
@@ -732,19 +738,15 @@
 
             try {
                 Bundle params = new Bundle();
+                Trace.beginSection("WPMS.getWallpaperWithFeature_" + which);
                 ParcelFileDescriptor pfd = mService.getWallpaperWithFeature(
                         context.getOpPackageName(), context.getAttributionTag(), this, which,
                         params, userId, /* getCropped = */ true);
+                Trace.endSection();
 
                 if (pfd != null) {
-                    try (BufferedInputStream bis = new BufferedInputStream(
-                            new ParcelFileDescriptor.AutoCloseInputStream(pfd))) {
-                        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
-                        int data;
-                        while ((data = bis.read()) != -1) {
-                            baos.write(data);
-                        }
-                        ImageDecoder.Source src = ImageDecoder.createSource(baos.toByteArray());
+                    try (InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
+                        ImageDecoder.Source src = ImageDecoder.createSource(is.readAllBytes());
                         return ImageDecoder.decodeBitmap(src, ((decoder, info, source) -> {
                             // Mutable and hardware config can't be set at the same time.
                             decoder.setMutableRequired(!hardware);
@@ -764,13 +766,18 @@
         }
 
         private Bitmap getDefaultWallpaper(Context context, @SetWallpaperFlags int which) {
+            Trace.beginSection("WPMS.getDefaultWallpaper_" + which);
             Bitmap defaultWallpaper = mDefaultWallpaper;
             if (defaultWallpaper == null || defaultWallpaper.isRecycled()) {
                 defaultWallpaper = null;
+                Trace.beginSection("WPMS.openDefaultWallpaper");
                 try (InputStream is = openDefaultWallpaper(context, which)) {
+                    Trace.endSection();
                     if (is != null) {
                         BitmapFactory.Options options = new BitmapFactory.Options();
+                        Trace.beginSection("WPMS.decodeStream");
                         defaultWallpaper = BitmapFactory.decodeStream(is, null, options);
+                        Trace.endSection();
                     }
                 } catch (OutOfMemoryError | IOException e) {
                     Log.w(TAG, "Can't decode stream", e);
@@ -779,6 +786,7 @@
             synchronized (this) {
                 mDefaultWallpaper = defaultWallpaper;
             }
+            Trace.endSection();
             return defaultWallpaper;
         }
 
diff --git a/core/java/android/app/usage/UsageEventsQuery.java b/core/java/android/app/usage/UsageEventsQuery.java
index 8c63d18..3cd2923 100644
--- a/core/java/android/app/usage/UsageEventsQuery.java
+++ b/core/java/android/app/usage/UsageEventsQuery.java
@@ -19,9 +19,11 @@
 import android.annotation.CurrentTimeMillisLong;
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
+import android.annotation.UserIdInt;
 import android.app.usage.UsageEvents.Event;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.UserHandle;
 import android.util.ArraySet;
 
 import com.android.internal.util.ArrayUtils;
@@ -40,11 +42,13 @@
     private final @CurrentTimeMillisLong long mBeginTimeMillis;
     private final @CurrentTimeMillisLong long mEndTimeMillis;
     private final @Event.EventType int[] mEventTypes;
+    private final @UserIdInt int mUserId;
 
     private UsageEventsQuery(@NonNull Builder builder) {
         mBeginTimeMillis = builder.mBeginTimeMillis;
         mEndTimeMillis = builder.mEndTimeMillis;
         mEventTypes = ArrayUtils.convertToIntArray(builder.mEventTypes);
+        mUserId = builder.mUserId;
     }
 
     private UsageEventsQuery(Parcel in) {
@@ -53,6 +57,7 @@
         int eventTypesLength = in.readInt();
         mEventTypes = new int[eventTypesLength];
         in.readIntArray(mEventTypes);
+        mUserId = in.readInt();
     }
 
     /**
@@ -87,6 +92,11 @@
         return eventTypeSet;
     }
 
+    /** @hide */
+    public @UserIdInt int getUserId() {
+        return mUserId;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -98,6 +108,7 @@
         dest.writeLong(mEndTimeMillis);
         dest.writeInt(mEventTypes.length);
         dest.writeIntArray(mEventTypes);
+        dest.writeInt(mUserId);
     }
 
     @NonNull
@@ -126,6 +137,7 @@
         private final @CurrentTimeMillisLong long mBeginTimeMillis;
         private final @CurrentTimeMillisLong long mEndTimeMillis;
         private final ArraySet<Integer> mEventTypes = new ArraySet<>();
+        private @UserIdInt int mUserId = UserHandle.USER_NULL;
 
         /**
          * Constructor that specifies the period for which to return events.
@@ -169,5 +181,15 @@
             }
             return this;
         }
+
+        /**
+         * Specifices the user id for the query.
+         * @param userId for whom the query should be performed.
+         * @hide
+         */
+        public @NonNull Builder setUserId(@UserIdInt int userId) {
+            mUserId = userId;
+            return this;
+        }
     }
 }
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl b/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl
index fac44b5..44942d6 100644
--- a/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl
+++ b/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,11 +16,11 @@
 package android.companion.virtual.camera;
 
 import android.companion.virtual.camera.VirtualCameraStreamConfig;
-import android.companion.virtual.camera.VirtualCameraMetadata;
 import android.view.Surface;
 
 /**
- * Interface for the virtual camera service and system server to talk back to the virtual camera owner.
+ * Interface for the virtual camera service and system server to talk back to the virtual camera
+ * owner.
  *
  * @hide
  */
@@ -40,8 +40,7 @@
             in VirtualCameraStreamConfig streamConfig);
 
     /**
-     * The client application is requesting a camera frame for the given streamId with the provided
-     * metadata.
+     * The client application is requesting a camera frame for the given streamId and frameId.
      *
      * <p>The virtual camera needs to write the frame data in the {@link Surface} corresponding to
      * this stream that was provided during the {@link #onStreamConfigured(int, Surface,
@@ -52,16 +51,14 @@
      *     VirtualCameraStreamConfig)}
      * @param frameId The frameId that is being requested. Each request will have a different
      *     frameId, that will be increasing for each call with a particular streamId.
-     * @param metadata The metadata requested for the frame. The virtual camera should do its best
-     *     to honor the requested metadata.
      */
-    oneway void onProcessCaptureRequest(
-            int streamId, long frameId, in VirtualCameraMetadata metadata);
+    oneway void onProcessCaptureRequest(int streamId, long frameId);
 
     /**
      * The stream previously configured when {@link #onStreamConfigured(int, Surface,
      * VirtualCameraStreamConfig)} was called is now being closed and associated resources can be
-     * freed. The Surface was disposed on the client side and should not be used anymore by the virtual camera owner
+     * freed. The Surface was disposed on the client side and should not be used anymore by the
+     * virtual camera owner.
      *
      * @param streamId The id of the stream that was closed.
      */
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraCallback.java b/core/java/android/companion/virtual/camera/VirtualCameraCallback.java
index a18ae03..5b658b8 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraCallback.java
+++ b/core/java/android/companion/virtual/camera/VirtualCameraCallback.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@
 
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.companion.virtual.flags.Flags;
 import android.view.Surface;
@@ -50,8 +49,7 @@
             @NonNull VirtualCameraStreamConfig streamConfig);
 
     /**
-     * The client application is requesting a camera frame for the given streamId with the provided
-     * metadata.
+     * The client application is requesting a camera frame for the given streamId and frameId.
      *
      * <p>The virtual camera needs to write the frame data in the {@link Surface} corresponding to
      * this stream that was provided during the {@link #onStreamConfigured(int, Surface,
@@ -62,12 +60,8 @@
      *     VirtualCameraStreamConfig)}
      * @param frameId The frameId that is being requested. Each request will have a different
      *     frameId, that will be increasing for each call with a particular streamId.
-     * @param metadata The metadata requested for the frame. The virtual camera should do its best
-     *     to honor the requested metadata but the consumer won't be informed about the metadata set
-     *     for a particular frame. If null, the requested frame can be anything the producer sends.
      */
-    void onProcessCaptureRequest(
-            int streamId, long frameId, @Nullable VirtualCameraMetadata metadata);
+    default void onProcessCaptureRequest(int streamId, long frameId) {}
 
     /**
      * The stream previously configured when {@link #onStreamConfigured(int, Surface,
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
index f1eb240..a939251 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
+++ b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -224,9 +224,8 @@
         }
 
         @Override
-        public void onProcessCaptureRequest(
-                int streamId, long frameId, VirtualCameraMetadata metadata) {
-            mExecutor.execute(() -> mCallback.onProcessCaptureRequest(streamId, frameId, metadata));
+        public void onProcessCaptureRequest(int streamId, long frameId) {
+            mExecutor.execute(() -> mCallback.onProcessCaptureRequest(streamId, frameId));
         }
 
         @Override
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.java b/core/java/android/companion/virtual/camera/VirtualCameraMetadata.java
deleted file mode 100644
index 1ba36d0..0000000
--- a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.companion.virtual.camera;
-
-import android.annotation.FlaggedApi;
-import android.annotation.NonNull;
-import android.annotation.SystemApi;
-import android.companion.virtual.flags.Flags;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Data structure used to store camera metadata compatible with VirtualCamera.
- *
- * @hide
- */
-@SystemApi
-@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
-public final class VirtualCameraMetadata implements Parcelable {
-
-    /** @hide */
-    public VirtualCameraMetadata(@NonNull Parcel in) {}
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {}
-
-    @NonNull
-    public static final Creator<VirtualCameraMetadata> CREATOR =
-            new Creator<>() {
-                @Override
-                @NonNull
-                public VirtualCameraMetadata createFromParcel(Parcel in) {
-                    return new VirtualCameraMetadata(in);
-                }
-
-                @Override
-                @NonNull
-                public VirtualCameraMetadata[] newArray(int size) {
-                    return new VirtualCameraMetadata[size];
-                }
-            };
-}
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index d13d962..12da665 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1393,6 +1393,18 @@
     public static final long OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION = 255940284L;
 
     /**
+     * Enables {@link #SCREEN_ORIENTATION_USER} which overrides any orientation requested
+     * by the activity. Fixed orientation apps can be overridden to fullscreen on large
+     * screen devices with ignoreOrientationRequest enabled with this override.
+     *
+     * @hide
+     */
+    @ChangeId
+    @Overridable
+    @Disabled
+    public static final long OVERRIDE_ANY_ORIENTATION_TO_USER = 310816437L;
+
+    /**
      * Compares activity window layout min width/height with require space for multi window to
      * determine if it can be put into multi window mode.
      */
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 98623de..6dc8d47 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -300,7 +300,8 @@
 
     String[] setPackagesSuspendedAsUser(in String[] packageNames, boolean suspended,
             in PersistableBundle appExtras, in PersistableBundle launcherExtras,
-            in SuspendDialogInfo dialogInfo, int flags, String callingPackage, int userId);
+            in SuspendDialogInfo dialogInfo, int flags, String suspendingPackage,
+            int suspendingUserId, int targetUserId);
 
     String[] getUnsuspendablePackagesForUser(in String[] packageNames, int userId);
 
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 82a8c11..a863870 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -10000,6 +10000,9 @@
      * device administrators or apps holding {@link android.Manifest.permission#MANAGE_USERS} or
      * {@link android.Manifest.permission#SUSPEND_APPS}.
      *
+     * <p>
+     * <strong>Note:</strong>This API doesn't support cross user suspension and should only be used
+     * for testing.
      * @param suspendedPackage The package that has been suspended.
      * @return Name of the package that suspended the given package. Returns {@code null} if the
      * given package is not currently suspended and the platform package name - i.e.
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index b04b7ba..60d5c14 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -115,3 +115,11 @@
     description: "Feature flag to fix duplicated PackageManager flag values"
     bug: "314815969"
 }
+
+flag {
+    name: "provide_info_of_apk_in_apex"
+    namespace: "package_manager_service"
+    description: "Feature flag to provide the information of APK-in-APEX"
+    bug: "306329516"
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 57025c2..9a1796f 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -56,3 +56,11 @@
     description: "Add support to lock private space automatically after a time period"
     bug: "303201022"
 }
+
+flag {
+    name: "avatar_sync"
+    namespace: "multiuser"
+    description: "Implement new Avatar Picker outside of SetttingsLib with ability to select avatars from Google Account and synchronise to any changes."
+    bug: "296829976"
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/content/res/FontScaleConverter.java b/core/java/android/content/res/FontScaleConverter.java
index 28525e2..088949e 100644
--- a/core/java/android/content/res/FontScaleConverter.java
+++ b/core/java/android/content/res/FontScaleConverter.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -16,15 +16,11 @@
 
 package android.content.res;
 
-import android.annotation.NonNull;
-import android.util.MathUtils;
 
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.util.Arrays;
+import android.annotation.FlaggedApi;
 
 /**
- * A lookup table for non-linear font scaling. Converts font sizes given in "sp" dimensions to a
+ * A converter for non-linear font scaling. Converts font sizes given in "sp" dimensions to a
  * "dp" dimension according to a non-linear curve.
  *
  * <p>This is meant to improve readability at larger font scales: larger fonts will scale up more
@@ -32,131 +28,16 @@
  *
  * <p>The thinking here is that large fonts are already big enough to read, but we still want to
  * scale them slightly to preserve the visual hierarchy when compared to smaller fonts.
- *
- * @hide
  */
-public class FontScaleConverter {
-
-    @VisibleForTesting
-    final float[] mFromSpValues;
-    @VisibleForTesting
-    final float[] mToDpValues;
+@FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC)
+public interface FontScaleConverter {
+    /**
+     * Converts a dimension in "sp" to "dp".
+     */
+    float convertSpToDp(float sp);
 
     /**
-     * Creates a lookup table for the given conversions.
-     *
-     * <p>Any "sp" value not in the lookup table will be derived via linear interpolation.
-     *
-     * <p>The arrays must be sorted ascending and monotonically increasing.
-     *
-     * @param fromSp array of dimensions in SP
-     * @param toDp array of dimensions in DP that correspond to an SP value in fromSp
-     *
-     * @throws IllegalArgumentException if the array lengths don't match or are empty
-     * @hide
+     * Converts a dimension in "dp" back to "sp".
      */
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    public FontScaleConverter(@NonNull float[] fromSp, @NonNull float[] toDp) {
-        if (fromSp.length != toDp.length || fromSp.length == 0) {
-            throw new IllegalArgumentException("Array lengths must match and be nonzero");
-        }
-
-        mFromSpValues = fromSp;
-        mToDpValues = toDp;
-    }
-
-    /**
-     * Convert a dimension in "dp" back to "sp" using the lookup table.
-     *
-     * @hide
-     */
-    public float convertDpToSp(float dp) {
-        return lookupAndInterpolate(dp, mToDpValues, mFromSpValues);
-    }
-
-    /**
-     * Convert a dimension in "sp" to "dp" using the lookup table.
-     *
-     * @hide
-     */
-    public float convertSpToDp(float sp) {
-        return lookupAndInterpolate(sp, mFromSpValues, mToDpValues);
-    }
-
-    private static float lookupAndInterpolate(
-            float sourceValue,
-            float[] sourceValues,
-            float[] targetValues
-    ) {
-        final float sourceValuePositive = Math.abs(sourceValue);
-        // TODO(b/247861374): find a match at a higher index?
-        final float sign = Math.signum(sourceValue);
-        // We search for exact matches only, even if it's just a little off. The interpolation will
-        // handle any non-exact matches.
-        final int index = Arrays.binarySearch(sourceValues, sourceValuePositive);
-        if (index >= 0) {
-            // exact match, return the matching dp
-            return sign * targetValues[index];
-        } else {
-            // must be a value in between index and index + 1: interpolate.
-            final int lowerIndex = -(index + 1) - 1;
-
-            final float startSp;
-            final float endSp;
-            final float startDp;
-            final float endDp;
-
-            if (lowerIndex >= sourceValues.length - 1) {
-                // It's past our lookup table. Determine the last elements' scaling factor and use.
-                startSp = sourceValues[sourceValues.length - 1];
-                startDp = targetValues[sourceValues.length - 1];
-
-                if (startSp == 0) return 0;
-
-                final float scalingFactor = startDp / startSp;
-                return sourceValue * scalingFactor;
-            } else if (lowerIndex == -1) {
-                // It's smaller than the smallest value in our table. Interpolate from 0.
-                startSp = 0;
-                startDp = 0;
-                endSp = sourceValues[0];
-                endDp = targetValues[0];
-            } else {
-                startSp = sourceValues[lowerIndex];
-                endSp = sourceValues[lowerIndex + 1];
-                startDp = targetValues[lowerIndex];
-                endDp = targetValues[lowerIndex + 1];
-            }
-
-            return sign
-                    * MathUtils.constrainedMap(startDp, endDp, startSp, endSp, sourceValuePositive);
-        }
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null) return false;
-        if (!(o instanceof FontScaleConverter)) return false;
-        FontScaleConverter that = (FontScaleConverter) o;
-        return Arrays.equals(mFromSpValues, that.mFromSpValues)
-                && Arrays.equals(mToDpValues, that.mToDpValues);
-    }
-
-    @Override
-    public int hashCode() {
-        int result = Arrays.hashCode(mFromSpValues);
-        result = 31 * result + Arrays.hashCode(mToDpValues);
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        return "FontScaleConverter{"
-                + "fromSpValues="
-                + Arrays.toString(mFromSpValues)
-                + ", toDpValues="
-                + Arrays.toString(mToDpValues)
-                + '}';
-    }
+    float convertDpToSp(float dp);
 }
diff --git a/core/java/android/content/res/FontScaleConverterFactory.java b/core/java/android/content/res/FontScaleConverterFactory.java
index 5eb6526..5d31cc0 100644
--- a/core/java/android/content/res/FontScaleConverterFactory.java
+++ b/core/java/android/content/res/FontScaleConverterFactory.java
@@ -16,6 +16,8 @@
 
 package android.content.res;
 
+import android.annotation.AnyThread;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.util.MathUtils;
@@ -24,67 +26,88 @@
 import com.android.internal.annotations.VisibleForTesting;
 
 /**
- * Stores lookup tables for creating {@link FontScaleConverter}s at various scales.
+ * Creates {@link FontScaleConverter}s at various scales.
  *
- * @hide
+ * Generally you shouldn't need this; you can use {@link
+ * android.util.TypedValue#applyDimension(int, float, DisplayMetrics)} directly and it will do the
+ * scaling conversion for you. But for UI frameworks or other situations where you need to do the
+ * conversion without an Android Context, you can use this class.
  */
+@FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC)
 public class FontScaleConverterFactory {
     private static final float SCALE_KEY_MULTIPLIER = 100f;
 
+    /** @hide */
+    // GuardedBy("LOOKUP_TABLES_WRITE_LOCK") but only for writes!
     @VisibleForTesting
-    static final SparseArray<FontScaleConverter> LOOKUP_TABLES = new SparseArray<>();
+    @NonNull
+    public static volatile SparseArray<FontScaleConverter> sLookupTables = new SparseArray<>();
+
+    /**
+     * This is a write lock only! We don't care about synchronization on reads; they can be a bit
+     * out of date. But all writes have to be atomic, so we use this similar to a
+     * CopyOnWriteArrayList.
+     */
+    private static final Object LOOKUP_TABLES_WRITE_LOCK = new Object();
 
     private static float sMinScaleBeforeCurvesApplied = 1.05f;
 
     static {
         // These were generated by frameworks/base/tools/fonts/font-scaling-array-generator.js and
         // manually tweaked for optimum readability.
-        put(
-                /* scaleKey= */ 1.15f,
-                new FontScaleConverter(
-                        /* fromSp= */
-                        new float[] {   8f,   10f,   12f,   14f,   18f,   20f,   24f,   30f,  100},
-                        /* toDp=   */
-                        new float[] { 9.2f, 11.5f, 13.8f, 16.4f, 19.8f, 21.8f, 25.2f,   30f,  100})
-        );
+        synchronized (LOOKUP_TABLES_WRITE_LOCK) {
+            putInto(
+                    sLookupTables,
+                    /* scaleKey= */ 1.15f,
+                    new FontScaleConverterImpl(
+                            /* fromSp= */
+                            new float[] {   8f,   10f,   12f,   14f,   18f,   20f,   24f,   30f,  100},
+                            /* toDp=   */
+                            new float[] { 9.2f, 11.5f, 13.8f, 16.4f, 19.8f, 21.8f, 25.2f,   30f,  100})
+            );
 
-        put(
-                /* scaleKey= */ 1.3f,
-                new FontScaleConverter(
-                        /* fromSp= */
-                        new float[] {   8f,   10f,   12f,   14f,   18f,   20f,   24f,   30f,  100},
-                        /* toDp=   */
-                        new float[] {10.4f,   13f, 15.6f, 18.8f, 21.6f, 23.6f, 26.4f,   30f,  100})
-        );
+            putInto(
+                    sLookupTables,
+                    /* scaleKey= */ 1.3f,
+                    new FontScaleConverterImpl(
+                            /* fromSp= */
+                            new float[] {   8f,   10f,   12f,   14f,   18f,   20f,   24f,   30f,  100},
+                            /* toDp=   */
+                            new float[] {10.4f,   13f, 15.6f, 18.8f, 21.6f, 23.6f, 26.4f,   30f,  100})
+            );
 
-        put(
-                /* scaleKey= */ 1.5f,
-                new FontScaleConverter(
-                        /* fromSp= */
-                        new float[] {   8f,   10f,   12f,   14f,   18f,   20f,   24f,   30f,  100},
-                        /* toDp=   */
-                        new float[] {  12f,   15f,   18f,   22f,   24f,   26f,   28f,   30f,  100})
-        );
+            putInto(
+                    sLookupTables,
+                    /* scaleKey= */ 1.5f,
+                    new FontScaleConverterImpl(
+                            /* fromSp= */
+                            new float[] {   8f,   10f,   12f,   14f,   18f,   20f,   24f,   30f,  100},
+                            /* toDp=   */
+                            new float[] {  12f,   15f,   18f,   22f,   24f,   26f,   28f,   30f,  100})
+            );
 
-        put(
-                /* scaleKey= */ 1.8f,
-                new FontScaleConverter(
-                        /* fromSp= */
-                        new float[] {   8f,   10f,   12f,   14f,   18f,   20f,   24f,   30f,  100},
-                        /* toDp=   */
-                        new float[] {14.4f,   18f, 21.6f, 24.4f, 27.6f, 30.8f, 32.8f, 34.8f,  100})
-        );
+            putInto(
+                    sLookupTables,
+                    /* scaleKey= */ 1.8f,
+                    new FontScaleConverterImpl(
+                            /* fromSp= */
+                            new float[] {   8f,   10f,   12f,   14f,   18f,   20f,   24f,   30f,  100},
+                            /* toDp=   */
+                            new float[] {14.4f,   18f, 21.6f, 24.4f, 27.6f, 30.8f, 32.8f, 34.8f,  100})
+            );
 
-        put(
-                /* scaleKey= */ 2f,
-                new FontScaleConverter(
-                        /* fromSp= */
-                        new float[] {   8f,   10f,   12f,   14f,   18f,   20f,   24f,   30f,  100},
-                        /* toDp=   */
-                        new float[] {  16f,   20f,   24f,   26f,   30f,   34f,   36f,   38f,  100})
-        );
+            putInto(
+                    sLookupTables,
+                    /* scaleKey= */ 2f,
+                    new FontScaleConverterImpl(
+                            /* fromSp= */
+                            new float[] {   8f,   10f,   12f,   14f,   18f,   20f,   24f,   30f,  100},
+                            /* toDp=   */
+                            new float[] {  16f,   20f,   24f,   26f,   30f,   34f,   36f,   38f,  100})
+            );
+        }
 
-        sMinScaleBeforeCurvesApplied = getScaleFromKey(LOOKUP_TABLES.keyAt(0)) - 0.02f;
+        sMinScaleBeforeCurvesApplied = getScaleFromKey(sLookupTables.keyAt(0)) - 0.02f;
         if (sMinScaleBeforeCurvesApplied <= 1.0f) {
             throw new IllegalStateException(
                     "You should only apply non-linear scaling to font scales > 1"
@@ -100,9 +123,9 @@
      *
      * <p>Example usage:
      * <code>isNonLinearFontScalingActive(getResources().getConfiguration().fontScale)</code>
-     *
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC)
+    @AnyThread
     public static boolean isNonLinearFontScalingActive(float fontScale) {
         return fontScale >= sMinScaleBeforeCurvesApplied;
     }
@@ -113,10 +136,10 @@
      * @param fontScale the scale factor, usually from {@link Configuration#fontScale}.
      *
      * @return a converter for the given scale, or null if non-linear scaling should not be used.
-     *
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC)
     @Nullable
+    @AnyThread
     public static FontScaleConverter forScale(float fontScale) {
         if (!isNonLinearFontScalingActive(fontScale)) {
             return null;
@@ -128,23 +151,33 @@
         }
 
         // Didn't find an exact match: interpolate between two existing tables
-        final int index = LOOKUP_TABLES.indexOfKey(getKey(fontScale));
+        final int index = sLookupTables.indexOfKey(getKey(fontScale));
         if (index >= 0) {
             // This should never happen, should have been covered by get() above.
-            return LOOKUP_TABLES.valueAt(index);
+            return sLookupTables.valueAt(index);
         }
         // Didn't find an exact match: interpolate between two existing tables
         final int lowerIndex = -(index + 1) - 1;
         final int higherIndex = lowerIndex + 1;
-        if (lowerIndex < 0 || higherIndex >= LOOKUP_TABLES.size()) {
+        if (lowerIndex < 0 || higherIndex >= sLookupTables.size()) {
             // We have gone beyond our bounds and have nothing to interpolate between. Just give
             // them a straight linear table instead.
             // This works because when FontScaleConverter encounters a size beyond its bounds, it
             // calculates a linear fontScale factor using the ratio of the last element pair.
-            return new FontScaleConverter(new float[] {1f}, new float[] {fontScale});
+            FontScaleConverterImpl converter = new FontScaleConverterImpl(
+                    new float[]{1f},
+                    new float[]{fontScale}
+            );
+
+            if (Flags.fontScaleConverterPublic()) {
+                // Cache for next time.
+                put(fontScale, converter);
+            }
+
+            return converter;
         } else {
-            float startScale = getScaleFromKey(LOOKUP_TABLES.keyAt(lowerIndex));
-            float endScale = getScaleFromKey(LOOKUP_TABLES.keyAt(higherIndex));
+            float startScale = getScaleFromKey(sLookupTables.keyAt(lowerIndex));
+            float endScale = getScaleFromKey(sLookupTables.keyAt(higherIndex));
             float interpolationPoint = MathUtils.constrainedMap(
                     /* rangeMin= */ 0f,
                     /* rangeMax= */ 1f,
@@ -152,10 +185,18 @@
                     endScale,
                     fontScale
             );
-            return createInterpolatedTableBetween(
-                    LOOKUP_TABLES.valueAt(lowerIndex),
-                    LOOKUP_TABLES.valueAt(higherIndex),
-                    interpolationPoint);
+            FontScaleConverter converter = createInterpolatedTableBetween(
+                    sLookupTables.valueAt(lowerIndex),
+                    sLookupTables.valueAt(higherIndex),
+                    interpolationPoint
+            );
+
+            if (Flags.fontScaleConverterPublic()) {
+                // Cache for next time.
+                put(fontScale, converter);
+            }
+
+            return converter;
         }
     }
 
@@ -175,7 +216,7 @@
             dpInterpolated[i] = MathUtils.lerp(startDp, endDp, interpolationPoint);
         }
 
-        return new FontScaleConverter(commonSpSizes, dpInterpolated);
+        return new FontScaleConverterImpl(commonSpSizes, dpInterpolated);
     }
 
     private static int getKey(float fontScale) {
@@ -187,11 +228,24 @@
     }
 
     private static void put(float scaleKey, @NonNull FontScaleConverter fontScaleConverter) {
-        LOOKUP_TABLES.put(getKey(scaleKey), fontScaleConverter);
+        // Dollar-store CopyOnWriteSparseArray, since this is the only write op we need.
+        synchronized (LOOKUP_TABLES_WRITE_LOCK) {
+            var newTable = sLookupTables.clone();
+            putInto(newTable, scaleKey, fontScaleConverter);
+            sLookupTables = newTable;
+        }
+    }
+
+    private static void putInto(
+            SparseArray<FontScaleConverter> table,
+            float scaleKey,
+            @NonNull FontScaleConverter fontScaleConverter
+    ) {
+        table.put(getKey(scaleKey), fontScaleConverter);
     }
 
     @Nullable
     private static FontScaleConverter get(float scaleKey) {
-        return LOOKUP_TABLES.get(getKey(scaleKey));
+        return sLookupTables.get(getKey(scaleKey));
     }
 }
diff --git a/core/java/android/content/res/FontScaleConverterImpl.java b/core/java/android/content/res/FontScaleConverterImpl.java
new file mode 100644
index 0000000..1968c4e
--- /dev/null
+++ b/core/java/android/content/res/FontScaleConverterImpl.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res;
+
+import android.annotation.NonNull;
+import android.util.MathUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Arrays;
+
+/**
+ * A lookup table for non-linear font scaling. Converts font sizes given in "sp" dimensions to a
+ * "dp" dimension according to a non-linear curve by interpolating values in a lookup table.
+ *
+ * {@see FontScaleConverter}
+ *
+ * @hide
+ */
+// Needs to be public so the Kotlin test can see it
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class FontScaleConverterImpl implements FontScaleConverter {
+
+    /** @hide */
+    @VisibleForTesting
+    public final float[] mFromSpValues;
+    /** @hide */
+    @VisibleForTesting
+    public final float[] mToDpValues;
+
+    /**
+     * Creates a lookup table for the given conversions.
+     *
+     * <p>Any "sp" value not in the lookup table will be derived via linear interpolation.
+     *
+     * <p>The arrays must be sorted ascending and monotonically increasing.
+     *
+     * @param fromSp array of dimensions in SP
+     * @param toDp array of dimensions in DP that correspond to an SP value in fromSp
+     *
+     * @throws IllegalArgumentException if the array lengths don't match or are empty
+     * @hide
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public FontScaleConverterImpl(@NonNull float[] fromSp, @NonNull float[] toDp) {
+        if (fromSp.length != toDp.length || fromSp.length == 0) {
+            throw new IllegalArgumentException("Array lengths must match and be nonzero");
+        }
+
+        mFromSpValues = fromSp;
+        mToDpValues = toDp;
+    }
+
+    /**
+     * Convert a dimension in "dp" back to "sp" using the lookup table.
+     *
+     * @hide
+     */
+    @Override
+    public float convertDpToSp(float dp) {
+        return lookupAndInterpolate(dp, mToDpValues, mFromSpValues);
+    }
+
+    /**
+     * Convert a dimension in "sp" to "dp" using the lookup table.
+     *
+     * @hide
+     */
+    @Override
+    public float convertSpToDp(float sp) {
+        return lookupAndInterpolate(sp, mFromSpValues, mToDpValues);
+    }
+
+    private static float lookupAndInterpolate(
+            float sourceValue,
+            float[] sourceValues,
+            float[] targetValues
+    ) {
+        final float sourceValuePositive = Math.abs(sourceValue);
+        // TODO(b/247861374): find a match at a higher index?
+        final float sign = Math.signum(sourceValue);
+        // We search for exact matches only, even if it's just a little off. The interpolation will
+        // handle any non-exact matches.
+        final int index = Arrays.binarySearch(sourceValues, sourceValuePositive);
+        if (index >= 0) {
+            // exact match, return the matching dp
+            return sign * targetValues[index];
+        } else {
+            // must be a value in between index and index + 1: interpolate.
+            final int lowerIndex = -(index + 1) - 1;
+
+            final float startSp;
+            final float endSp;
+            final float startDp;
+            final float endDp;
+
+            if (lowerIndex >= sourceValues.length - 1) {
+                // It's past our lookup table. Determine the last elements' scaling factor and use.
+                startSp = sourceValues[sourceValues.length - 1];
+                startDp = targetValues[sourceValues.length - 1];
+
+                if (startSp == 0) return 0;
+
+                final float scalingFactor = startDp / startSp;
+                return sourceValue * scalingFactor;
+            } else if (lowerIndex == -1) {
+                // It's smaller than the smallest value in our table. Interpolate from 0.
+                startSp = 0;
+                startDp = 0;
+                endSp = sourceValues[0];
+                endDp = targetValues[0];
+            } else {
+                startSp = sourceValues[lowerIndex];
+                endSp = sourceValues[lowerIndex + 1];
+                startDp = targetValues[lowerIndex];
+                endDp = targetValues[lowerIndex + 1];
+            }
+
+            return sign
+                    * MathUtils.constrainedMap(startDp, endDp, startSp, endSp, sourceValuePositive);
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null) return false;
+        if (!(o instanceof FontScaleConverterImpl)) return false;
+        FontScaleConverterImpl that = (FontScaleConverterImpl) o;
+        return Arrays.equals(mFromSpValues, that.mFromSpValues)
+                && Arrays.equals(mToDpValues, that.mToDpValues);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = Arrays.hashCode(mFromSpValues);
+        result = 31 * result + Arrays.hashCode(mToDpValues);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "FontScaleConverter{"
+                + "fromSpValues="
+                + Arrays.toString(mFromSpValues)
+                + ", toDpValues="
+                + Arrays.toString(mToDpValues)
+                + '}';
+    }
+}
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index 3a00d91..db81e84 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -10,6 +10,15 @@
 }
 
 flag {
+    name: "font_scale_converter_public"
+    namespace: "accessibility"
+    description: "Enables the public API for FontScaleConverter, including enabling thread-safe caching."
+    bug: "239736383"
+    # fixed_read_only or device wont boot because of permission issues accessing flags during boot
+    is_fixed_read_only: true
+}
+
+flag {
     name: "asset_file_descriptor_frro"
     namespace: "resource_manager"
     description: "Feature flag for passing in an AssetFileDescriptor to create an frro"
diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java
index 52bba14..870546a 100644
--- a/core/java/android/database/CursorWindow.java
+++ b/core/java/android/database/CursorWindow.java
@@ -22,14 +22,8 @@
 import android.content.res.Resources;
 import android.database.sqlite.SQLiteClosable;
 import android.database.sqlite.SQLiteException;
-import android.os.Binder;
-import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.os.Process;
-import android.util.Log;
-import android.util.LongSparseArray;
-import android.util.SparseIntArray;
 
 import dalvik.annotation.optimization.FastNative;
 import dalvik.system.CloseGuard;
@@ -44,6 +38,9 @@
  * consumer for reading.
  * </p>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+@android.ravenwood.annotation.RavenwoodNativeSubstitutionClass(
+        "com.android.hoststubgen.nativesubstitution.CursorWindow_host")
 public class CursorWindow extends SQLiteClosable implements Parcelable {
     private static final String STATS_TAG = "CursorWindowStats";
 
@@ -61,7 +58,7 @@
     private int mStartPos;
     private final String mName;
 
-    private final CloseGuard mCloseGuard = CloseGuard.get();
+    private final CloseGuard mCloseGuard;
 
     // May throw CursorWindowAllocationException
     private static native long nativeCreate(String name, int cursorWindowSize);
@@ -147,7 +144,7 @@
         if (mWindowPtr == 0) {
             throw new AssertionError(); // Not possible, the native code won't return it.
         }
-        mCloseGuard.open("CursorWindow.close");
+        mCloseGuard = createCloseGuard();
     }
 
     /**
@@ -175,7 +172,18 @@
             throw new AssertionError(); // Not possible, the native code won't return it.
         }
         mName = nativeGetName(mWindowPtr);
-        mCloseGuard.open("CursorWindow.close");
+        mCloseGuard = createCloseGuard();
+    }
+
+    @android.ravenwood.annotation.RavenwoodReplace
+    private CloseGuard createCloseGuard() {
+        final CloseGuard closeGuard = CloseGuard.get();
+        closeGuard.open("CursorWindow.close");
+        return closeGuard;
+    }
+
+    private CloseGuard createCloseGuard$ravenwood() {
+        return null;
     }
 
     @Override
@@ -749,6 +757,7 @@
         dispose();
     }
 
+    @android.ravenwood.annotation.RavenwoodReplace
     private static int getCursorWindowSize() {
         if (sCursorWindowSize < 0) {
             // The cursor window size. resource xml file specifies the value in kB.
@@ -759,6 +768,10 @@
         return sCursorWindowSize;
     }
 
+    private static int getCursorWindowSize$ravenwood() {
+        return 1024;
+    }
+
     @Override
     public String toString() {
         return getName() + " {" + Long.toHexString(mWindowPtr) + "}";
diff --git a/core/java/android/database/SQLException.java b/core/java/android/database/SQLException.java
index 3402026..4026f20 100644
--- a/core/java/android/database/SQLException.java
+++ b/core/java/android/database/SQLException.java
@@ -19,6 +19,7 @@
 /**
  * An exception that indicates there was an error with SQL parsing or execution.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class SQLException extends RuntimeException {
     public SQLException() {
     }
diff --git a/core/java/android/database/sqlite/SQLiteClosable.java b/core/java/android/database/sqlite/SQLiteClosable.java
index 2fca729..8eb512a 100644
--- a/core/java/android/database/sqlite/SQLiteClosable.java
+++ b/core/java/android/database/sqlite/SQLiteClosable.java
@@ -25,6 +25,7 @@
  *
  * This class implements a primitive reference counting scheme for database objects.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public abstract class SQLiteClosable implements Closeable {
     @UnsupportedAppUsage
     private int mReferenceCount = 1;
diff --git a/core/java/android/database/sqlite/SQLiteException.java b/core/java/android/database/sqlite/SQLiteException.java
index a1d9c9f..531b40a 100644
--- a/core/java/android/database/sqlite/SQLiteException.java
+++ b/core/java/android/database/sqlite/SQLiteException.java
@@ -21,6 +21,7 @@
 /**
  * A SQLite exception that indicates there was an error with SQL parsing or execution.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class SQLiteException extends SQLException {
     public SQLiteException() {
     }
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index f71e853..ffd7212 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -715,6 +715,14 @@
         boolean startOffload();
 
         void stopOffload();
+
+        /**
+         * Called when {@link DisplayOffloadSession} tries to block screen turning on.
+         *
+         * @param unblocker a {@link Runnable} executed upon all work required before screen turning
+         *                  on is done.
+         */
+        void onBlockingScreenOn(Runnable unblocker);
     }
 
     /** A session token that associates a internal display with a {@link DisplayOffloader}. */
@@ -734,6 +742,15 @@
          */
         void updateBrightness(float brightness);
 
+        /**
+         * Called while display is turning to state ON to leave a small period for displayoffload
+         * session to finish some work.
+         *
+         * @param unblocker a {@link Runnable} used by displayoffload session to notify
+         *                  {@link DisplayManager} that it can continue turning screen on.
+         */
+        boolean blockScreenOn(Runnable unblocker);
+
         /** Returns whether displayoffload supports the given display state. */
         static boolean isSupportedOffloadState(int displayState) {
             return Display.isSuspendedState(displayState);
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl b/core/java/android/hardware/face/FaceSensorConfigurations.aidl
similarity index 74%
rename from core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl
rename to core/java/android/hardware/face/FaceSensorConfigurations.aidl
index 6c1f0fc..26367b3 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl
+++ b/core/java/android/hardware/face/FaceSensorConfigurations.aidl
@@ -13,12 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package android.hardware.face;
 
-package android.companion.virtual.camera;
-
-/**
- * Data structure used to store {@link android.hardware.camera2.CameraMetadata} compatible with
- * VirtualCamera.
- * @hide
- */
-parcelable VirtualCameraMetadata;
+parcelable FaceSensorConfigurations;
\ No newline at end of file
diff --git a/core/java/android/hardware/face/FaceSensorConfigurations.java b/core/java/android/hardware/face/FaceSensorConfigurations.java
new file mode 100644
index 0000000..6ef692f
--- /dev/null
+++ b/core/java/android/hardware/face/FaceSensorConfigurations.java
@@ -0,0 +1,182 @@
+/*
+ * 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.hardware.face;
+
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.face.SensorProps;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Slog;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+
+/**
+ * Provides the sensor props for face sensor, if available.
+ * @hide
+ */
+public class FaceSensorConfigurations implements Parcelable {
+    private static final String TAG = "FaceSensorConfigurations";
+
+    private final boolean mResetLockoutRequiresChallenge;
+    private final Map<String, SensorProps[]> mSensorPropsMap;
+
+    public static final Creator<FaceSensorConfigurations> CREATOR =
+            new Creator<FaceSensorConfigurations>() {
+                @Override
+                public FaceSensorConfigurations createFromParcel(Parcel in) {
+                    return new FaceSensorConfigurations(in);
+                }
+
+                @Override
+                public FaceSensorConfigurations[] newArray(int size) {
+                    return new FaceSensorConfigurations[size];
+                }
+            };
+
+    public FaceSensorConfigurations(boolean resetLockoutRequiresChallenge) {
+        mResetLockoutRequiresChallenge = resetLockoutRequiresChallenge;
+        mSensorPropsMap = new HashMap<>();
+    }
+
+    protected FaceSensorConfigurations(Parcel in) {
+        mResetLockoutRequiresChallenge = in.readByte() != 0;
+        mSensorPropsMap = in.readHashMap(null, String.class, SensorProps[].class);
+    }
+
+    /**
+     * Process AIDL instances to extract sensor props and add it to the sensor map.
+     * @param aidlInstances available face AIDL instances
+     * @param getIFace function that provides the daemon for the specific instance
+     */
+    public void addAidlConfigs(@NonNull String[] aidlInstances,
+            @NonNull Function<String, IFace> getIFace) {
+        for (String aidlInstance : aidlInstances) {
+            final String fqName = IFace.DESCRIPTOR + "/" + aidlInstance;
+            IFace face = getIFace.apply(fqName);
+            try {
+                if (face != null) {
+                    mSensorPropsMap.put(aidlInstance, face.getSensorProps());
+                } else {
+                    Slog.e(TAG, "Unable to get declared service: " + fqName);
+                }
+            } catch (RemoteException e) {
+                Log.d(TAG, "Unable to get sensor properties!");
+            }
+        }
+    }
+
+    /**
+     * Parse through HIDL configuration and add it to the sensor map.
+     */
+    public void addHidlConfigs(@NonNull String[] hidlConfigStrings,
+            @NonNull Context context) {
+        final List<HidlFaceSensorConfig> hidlFaceSensorConfigs = new ArrayList<>();
+        for (String hidlConfig: hidlConfigStrings) {
+            final HidlFaceSensorConfig hidlFaceSensorConfig = new HidlFaceSensorConfig();
+            try {
+                hidlFaceSensorConfig.parse(hidlConfig, context);
+            } catch (Exception e) {
+                Log.e(TAG, "HIDL sensor configuration format is incorrect.");
+                continue;
+            }
+            if (hidlFaceSensorConfig.getModality() == TYPE_FACE) {
+                hidlFaceSensorConfigs.add(hidlFaceSensorConfig);
+            }
+        }
+        final String hidlHalInstanceName = "defaultHIDL";
+        mSensorPropsMap.put(hidlHalInstanceName, hidlFaceSensorConfigs.toArray(
+                new SensorProps[hidlFaceSensorConfigs.size()]));
+    }
+
+    /**
+     * Returns true if any face sensors have been added.
+     */
+    public boolean hasSensorConfigurations() {
+        return mSensorPropsMap.size() > 0;
+    }
+
+    /**
+     * Returns true if there is only a single face sensor configuration available.
+     */
+    public boolean isSingleSensorConfigurationPresent() {
+        return mSensorPropsMap.size() == 1;
+    }
+
+    /**
+     * Return sensor props for the given instance. If instance is not available,
+     * then null is returned.
+     */
+    @Nullable
+    public Pair<String, SensorProps[]> getSensorPairForInstance(String instance) {
+        if (mSensorPropsMap.containsKey(instance)) {
+            return new Pair<>(instance, mSensorPropsMap.get(instance));
+        }
+
+        return null;
+    }
+
+    /**
+     * Return the first pair of instance and sensor props, which does not correspond to the given
+     * If instance is not available, then null is returned.
+     */
+    @Nullable
+    public Pair<String, SensorProps[]> getSensorPairNotForInstance(String instance) {
+        Optional<String> notAVirtualInstance = mSensorPropsMap.keySet().stream().filter(
+                (instanceName) -> !instanceName.equals(instance)).findFirst();
+        return notAVirtualInstance.map(this::getSensorPairForInstance).orElseGet(
+                this::getSensorPair);
+    }
+
+    /**
+     * Returns the first pair of instance and sensor props that has been added to the map.
+     */
+    @Nullable
+    public Pair<String, SensorProps[]> getSensorPair() {
+        Optional<String> optionalInstance = mSensorPropsMap.keySet().stream().findFirst();
+        return optionalInstance.map(this::getSensorPairForInstance).orElse(null);
+
+    }
+
+    public boolean getResetLockoutRequiresChallenge() {
+        return mResetLockoutRequiresChallenge;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeByte((byte) (mResetLockoutRequiresChallenge ? 1 : 0));
+        dest.writeMap(mSensorPropsMap);
+    }
+}
diff --git a/core/java/android/hardware/face/HidlFaceSensorConfig.java b/core/java/android/hardware/face/HidlFaceSensorConfig.java
new file mode 100644
index 0000000..cab146d
--- /dev/null
+++ b/core/java/android/hardware/face/HidlFaceSensorConfig.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 android.hardware.face;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.common.CommonProps;
+import android.hardware.biometrics.common.SensorStrength;
+import android.hardware.biometrics.face.SensorProps;
+
+import com.android.internal.R;
+
+/**
+ * Parse HIDL face sensor config and map it to SensorProps.aidl to match AIDL.
+ * See core/res/res/values/config.xml config_biometric_sensors
+ * @hide
+ */
+public final class HidlFaceSensorConfig extends SensorProps {
+    private int mSensorId;
+    private int mModality;
+    private int mStrength;
+
+    /**
+     * Parse through the config string and map it to SensorProps.aidl.
+     * @throws IllegalArgumentException when config string has unexpected format
+     */
+    public void parse(@NonNull String config, @NonNull Context context)
+            throws IllegalArgumentException {
+        final String[] elems = config.split(":");
+        if (elems.length < 3) {
+            throw new IllegalArgumentException();
+        }
+        mSensorId = Integer.parseInt(elems[0]);
+        mModality = Integer.parseInt(elems[1]);
+        mStrength = Integer.parseInt(elems[2]);
+        mapHidlToAidlFaceSensorConfigurations(context);
+    }
+
+    @BiometricAuthenticator.Modality
+    public int getModality() {
+        return mModality;
+    }
+
+    private void mapHidlToAidlFaceSensorConfigurations(@NonNull Context context) {
+        commonProps = new CommonProps();
+        commonProps.sensorId = mSensorId;
+        commonProps.sensorStrength = authenticatorStrengthToPropertyStrength(mStrength);
+        halControlsPreview = context.getResources().getBoolean(
+                R.bool.config_faceAuthSupportsSelfIllumination);
+        commonProps.maxEnrollmentsPerUser = context.getResources().getInteger(
+                R.integer.config_faceMaxTemplatesPerUser);
+        commonProps.componentInfo = null;
+        supportsDetectInteraction = false;
+    }
+
+    private byte authenticatorStrengthToPropertyStrength(
+            @BiometricManager.Authenticators.Types int strength) {
+        switch (strength) {
+            case BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE:
+                return SensorStrength.CONVENIENCE;
+            case BiometricManager.Authenticators.BIOMETRIC_WEAK:
+                return SensorStrength.WEAK;
+            case BiometricManager.Authenticators.BIOMETRIC_STRONG:
+                return SensorStrength.STRONG;
+            default:
+                throw new IllegalArgumentException("Unknown strength: " + strength);
+        }
+    }
+}
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 7080133..0096877 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -26,6 +26,7 @@
 import android.hardware.face.Face;
 import android.hardware.face.FaceAuthenticateOptions;
 import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.FaceSensorConfigurations;
 import android.view.Surface;
 
 /**
@@ -167,6 +168,10 @@
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
     void registerAuthenticators(in List<FaceSensorPropertiesInternal> hidlSensors);
 
+    //Register all available face sensors.
+    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+    void registerAuthenticatorsLegacy(in FaceSensorConfigurations faceSensorConfigurations);
+
     // Adds a callback which gets called when the service registers all of the face
     // authenticators. The callback is automatically removed after it's invoked.
     void addAuthenticatorsRegisteredCallback(IFaceAuthenticatorsRegisteredCallback callback);
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl b/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.aidl
similarity index 74%
copy from core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl
copy to core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.aidl
index 6c1f0fc..ebb05dc 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl
+++ b/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.aidl
@@ -13,12 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package android.hardware.fingerprint;
 
-package android.companion.virtual.camera;
-
-/**
- * Data structure used to store {@link android.hardware.camera2.CameraMetadata} compatible with
- * VirtualCamera.
- * @hide
- */
-parcelable VirtualCameraMetadata;
+parcelable FingerprintSensorConfigurations;
\ No newline at end of file
diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java b/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java
new file mode 100644
index 0000000..f214494a
--- /dev/null
+++ b/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java
@@ -0,0 +1,184 @@
+/*
+ * 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.hardware.fingerprint;
+
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.SensorProps;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+
+/**
+ * Provides the sensor props for fingerprint sensor, if available.
+ * @hide
+ */
+
+public class FingerprintSensorConfigurations implements Parcelable {
+    private static final String TAG = "FingerprintSensorConfigurations";
+
+    private final Map<String, SensorProps[]> mSensorPropsMap;
+    private final boolean mResetLockoutRequiresHardwareAuthToken;
+
+    public static final Creator<FingerprintSensorConfigurations> CREATOR =
+            new Creator<>() {
+                @Override
+                public FingerprintSensorConfigurations createFromParcel(Parcel in) {
+                    return new FingerprintSensorConfigurations(in);
+                }
+
+                @Override
+                public FingerprintSensorConfigurations[] newArray(int size) {
+                    return new FingerprintSensorConfigurations[size];
+                }
+            };
+
+    public FingerprintSensorConfigurations(boolean resetLockoutRequiresHardwareAuthToken) {
+        mResetLockoutRequiresHardwareAuthToken = resetLockoutRequiresHardwareAuthToken;
+        mSensorPropsMap = new HashMap<>();
+    }
+
+    /**
+     * Process AIDL instances to extract sensor props and add it to the sensor map.
+     * @param aidlInstances available face AIDL instances
+     * @param getIFingerprint function that provides the daemon for the specific instance
+     */
+    public void addAidlSensors(@NonNull String[] aidlInstances,
+            @NonNull Function<String, IFingerprint> getIFingerprint) {
+        for (String aidlInstance : aidlInstances) {
+            try {
+                final String fqName = IFingerprint.DESCRIPTOR + "/" + aidlInstance;
+                final IFingerprint fp = getIFingerprint.apply(fqName);
+                if (fp != null) {
+                    SensorProps[] props = fp.getSensorProps();
+                    mSensorPropsMap.put(aidlInstance, props);
+                } else {
+                    Log.d(TAG, "IFingerprint null for instance " + aidlInstance);
+                }
+            } catch (RemoteException e) {
+                Log.d(TAG, "Unable to get sensor properties!");
+            }
+        }
+    }
+
+    /**
+     * Parse through HIDL configuration and add it to the sensor map.
+     */
+    public void addHidlSensors(@NonNull String[] hidlConfigStrings,
+            @NonNull Context context) {
+        final List<HidlFingerprintSensorConfig> hidlFingerprintSensorConfigs = new ArrayList<>();
+        for (String hidlConfigString : hidlConfigStrings) {
+            final HidlFingerprintSensorConfig hidlFingerprintSensorConfig =
+                    new HidlFingerprintSensorConfig();
+            try {
+                hidlFingerprintSensorConfig.parse(hidlConfigString, context);
+            } catch (Exception e) {
+                Log.e(TAG, "HIDL sensor configuration format is incorrect.");
+                continue;
+            }
+            if (hidlFingerprintSensorConfig.getModality() == TYPE_FINGERPRINT) {
+                hidlFingerprintSensorConfigs.add(hidlFingerprintSensorConfig);
+            }
+        }
+        final String hidlHalInstanceName = "defaultHIDL";
+        mSensorPropsMap.put(hidlHalInstanceName,
+                hidlFingerprintSensorConfigs.toArray(
+                        new HidlFingerprintSensorConfig[hidlFingerprintSensorConfigs.size()]));
+    }
+
+    protected FingerprintSensorConfigurations(Parcel in) {
+        mResetLockoutRequiresHardwareAuthToken = in.readByte() != 0;
+        mSensorPropsMap = in.readHashMap(null /* loader */, String.class, SensorProps[].class);
+    }
+
+    /**
+     * Returns true if any fingerprint sensors have been added.
+     */
+    public boolean hasSensorConfigurations() {
+        return mSensorPropsMap.size() > 0;
+    }
+
+    /**
+     * Returns true if there is only a single fingerprint sensor configuration available.
+     */
+    public boolean isSingleSensorConfigurationPresent() {
+        return mSensorPropsMap.size() == 1;
+    }
+
+    /**
+     * Return sensor props for the given instance. If instance is not available,
+     * then null is returned.
+     */
+    @Nullable
+    public Pair<String, SensorProps[]> getSensorPairForInstance(String instance) {
+        if (mSensorPropsMap.containsKey(instance)) {
+            return new Pair<>(instance, mSensorPropsMap.get(instance));
+        }
+
+        return null;
+    }
+
+    /**
+     * Return the first pair of instance and sensor props, which does not correspond to the given
+     * If instance is not available, then null is returned.
+     */
+    @Nullable
+    public Pair<String, SensorProps[]> getSensorPairNotForInstance(String instance) {
+        Optional<String> notAVirtualInstance = mSensorPropsMap.keySet().stream().filter(
+                (instanceName) -> !instanceName.equals(instance)).findFirst();
+        return notAVirtualInstance.map(this::getSensorPairForInstance).orElseGet(
+                this::getSensorPair);
+    }
+
+    /**
+     * Returns the first pair of instance and sensor props that has been added to the map.
+     */
+    @Nullable
+    public Pair<String, SensorProps[]> getSensorPair() {
+        Optional<String> optionalInstance = mSensorPropsMap.keySet().stream().findFirst();
+        return optionalInstance.map(this::getSensorPairForInstance).orElse(null);
+
+    }
+
+    public boolean getResetLockoutRequiresHardwareAuthToken() {
+        return mResetLockoutRequiresHardwareAuthToken;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeByte((byte) (mResetLockoutRequiresHardwareAuthToken ? 1 : 0));
+        dest.writeMap(mSensorPropsMap);
+    }
+}
diff --git a/core/java/android/hardware/fingerprint/HidlFingerprintSensorConfig.java b/core/java/android/hardware/fingerprint/HidlFingerprintSensorConfig.java
new file mode 100644
index 0000000..d481153
--- /dev/null
+++ b/core/java/android/hardware/fingerprint/HidlFingerprintSensorConfig.java
@@ -0,0 +1,119 @@
+/*
+ * 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.hardware.fingerprint;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.common.CommonProps;
+import android.hardware.biometrics.common.SensorStrength;
+import android.hardware.biometrics.fingerprint.FingerprintSensorType;
+import android.hardware.biometrics.fingerprint.SensorLocation;
+import android.hardware.biometrics.fingerprint.SensorProps;
+
+import com.android.internal.R;
+import com.android.internal.util.ArrayUtils;
+
+/**
+ * Parse HIDL fingerprint sensor config and map it to SensorProps.aidl to match AIDL.
+ * See core/res/res/values/config.xml config_biometric_sensors
+ * @hide
+ */
+public final class HidlFingerprintSensorConfig extends SensorProps {
+    private int mSensorId;
+    private int mModality;
+    private int mStrength;
+
+    /**
+     * Parse through the config string and map it to SensorProps.aidl.
+     * @throws IllegalArgumentException when config string has unexpected format
+     */
+    public void parse(@NonNull String config, @NonNull Context context)
+            throws IllegalArgumentException {
+        final String[] elems = config.split(":");
+        if (elems.length < 3) {
+            throw new IllegalArgumentException();
+        }
+        mSensorId = Integer.parseInt(elems[0]);
+        mModality = Integer.parseInt(elems[1]);
+        mStrength = Integer.parseInt(elems[2]);
+        mapHidlToAidlSensorConfiguration(context);
+    }
+
+    @BiometricAuthenticator.Modality
+    public int getModality() {
+        return mModality;
+    }
+
+    private void mapHidlToAidlSensorConfiguration(@NonNull Context context) {
+        commonProps = new CommonProps();
+        commonProps.componentInfo = null;
+        commonProps.sensorId = mSensorId;
+        commonProps.sensorStrength = authenticatorStrengthToPropertyStrength(mStrength);
+        commonProps.maxEnrollmentsPerUser = context.getResources().getInteger(
+                R.integer.config_fingerprintMaxTemplatesPerUser);
+        halControlsIllumination = false;
+        sensorLocations = new SensorLocation[1];
+
+        final int[] udfpsProps = context.getResources().getIntArray(
+                com.android.internal.R.array.config_udfps_sensor_props);
+        final boolean isUdfps = !ArrayUtils.isEmpty(udfpsProps);
+        // config_is_powerbutton_fps indicates whether device has a power button fingerprint sensor.
+        final boolean isPowerbuttonFps = context.getResources().getBoolean(
+                R.bool.config_is_powerbutton_fps);
+
+        if (isUdfps) {
+            sensorType = FingerprintSensorType.UNKNOWN;
+        } else if (isPowerbuttonFps) {
+            sensorType = FingerprintSensorType.POWER_BUTTON;
+        } else {
+            sensorType = FingerprintSensorType.REAR;
+        }
+
+        if (isUdfps && udfpsProps.length == 3) {
+            setSensorLocation(udfpsProps[0], udfpsProps[1], udfpsProps[2]);
+        } else {
+            setSensorLocation(540 /* sensorLocationX */, 1636 /* sensorLocationY */,
+                    130 /* sensorRadius */);
+        }
+
+    }
+
+    private void setSensorLocation(int sensorLocationX,
+            int sensorLocationY, int sensorRadius) {
+        sensorLocations[0] = new SensorLocation();
+        sensorLocations[0].display = "";
+        sensorLocations[0].sensorLocationX = sensorLocationX;
+        sensorLocations[0].sensorLocationY = sensorLocationY;
+        sensorLocations[0].sensorRadius = sensorRadius;
+    }
+
+    private byte authenticatorStrengthToPropertyStrength(
+            @BiometricManager.Authenticators.Types int strength) {
+        switch (strength) {
+            case BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE:
+                return SensorStrength.CONVENIENCE;
+            case BiometricManager.Authenticators.BIOMETRIC_WEAK:
+                return SensorStrength.WEAK;
+            case BiometricManager.Authenticators.BIOMETRIC_STRONG:
+                return SensorStrength.STRONG;
+            default:
+                throw new IllegalArgumentException("Unknown strength: " + strength);
+        }
+    }
+}
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index f594c00..606b171 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -31,6 +31,7 @@
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintSensorConfigurations;
 import java.util.List;
 
 /**
@@ -173,6 +174,10 @@
     @EnforcePermission("MANAGE_FINGERPRINT")
     void removeClientActiveCallback(IFingerprintClientActiveCallback callback);
 
+    //Register all available fingerprint sensors.
+    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+    void registerAuthenticatorsLegacy(in FingerprintSensorConfigurations fingerprintSensorConfigurations);
+
     // Registers all HIDL and AIDL sensors. Only HIDL sensor properties need to be provided, because
     // AIDL sensor properties are retrieved directly from the available HALs. If no HIDL HALs exist,
     // hidlSensors must be non-null and empty. See AuthService.java
diff --git a/core/java/android/os/HidlSupport.java b/core/java/android/os/HidlSupport.java
index 91b796a..7716055 100644
--- a/core/java/android/os/HidlSupport.java
+++ b/core/java/android/os/HidlSupport.java
@@ -218,6 +218,13 @@
     @SystemApi
     public static native int getPidIfSharable();
 
+    /**
+     * Return true if HIDL is supported on this device and false if not.
+     *
+     * @hide
+     */
+    public static native boolean isHidlSupported();
+
     /** @hide */
     public HidlSupport() {}
 }
diff --git a/core/java/android/os/HwBinder.java b/core/java/android/os/HwBinder.java
index feed208..bc19655 100644
--- a/core/java/android/os/HwBinder.java
+++ b/core/java/android/os/HwBinder.java
@@ -18,6 +18,7 @@
 
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.util.Log;
 
 import libcore.util.NativeAllocationRegistry;
 
@@ -78,6 +79,17 @@
             String iface,
             String serviceName)
         throws RemoteException, NoSuchElementException {
+        if (!HidlSupport.isHidlSupported()
+                && (iface.equals("android.hidl.manager@1.0::IServiceManager")
+                        || iface.equals("android.hidl.manager@1.1::IServiceManager")
+                        || iface.equals("android.hidl.manager@1.2::IServiceManager"))) {
+            Log.i(
+                    TAG,
+                    "Replacing Java hwservicemanager with a fake HwNoService"
+                            + " because HIDL is not supported on this device.");
+            return new HwNoService();
+        }
+
         return getService(iface, serviceName, false /* retry */);
     }
     /**
diff --git a/core/java/android/os/HwNoService.java b/core/java/android/os/HwNoService.java
new file mode 100644
index 0000000..117c3ad
--- /dev/null
+++ b/core/java/android/os/HwNoService.java
@@ -0,0 +1,52 @@
+/*
+ * 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.os;
+
+/**
+ * A fake hwservicemanager that is used locally when HIDL isn't supported on the device.
+ *
+ * @hide
+ */
+final class HwNoService implements IHwBinder, IHwInterface {
+    /** @hide */
+    @Override
+    public void transact(int code, HwParcel request, HwParcel reply, int flags) {}
+
+    /** @hide */
+    @Override
+    public IHwInterface queryLocalInterface(String descriptor) {
+        return new HwNoService();
+    }
+
+    /** @hide */
+    @Override
+    public boolean linkToDeath(DeathRecipient recipient, long cookie) {
+        return true;
+    }
+
+    /** @hide */
+    @Override
+    public boolean unlinkToDeath(DeathRecipient recipient) {
+        return true;
+    }
+
+    /** @hide */
+    @Override
+    public IHwBinder asBinder() {
+        return this;
+    }
+}
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 4bfff16..7d00b80 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -6911,7 +6911,11 @@
          * <td></td>
          * </tr>
          * </table>
+         *
+         * @deprecated This field may not be well supported by some contacts apps and is discouraged
+         * to use.
          */
+        @Deprecated
         public static final class Im implements DataColumnsWithJoins, CommonColumns, ContactCounts {
             /**
              * This utility class cannot be instantiated
@@ -7721,7 +7725,11 @@
          * <td></td>
          * </tr>
          * </table>
+         *
+         * @deprecated This field may not be well supported by some contacts apps and is discouraged
+         * to use.
          */
+        @Deprecated
         public static final class SipAddress implements DataColumnsWithJoins, CommonColumns,
                 ContactCounts {
             /**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 290ae4a..4af657d 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7330,7 +7330,7 @@
                 "hearing_aid_media_routing";
 
         /**
-         * System sounds routing value for hearing aid. It routes system sounds to hearing aid
+         * Notification routing value for hearing aid. It routes notification sounds to hearing aid
          * or device speaker.
          * <ul>
          *     <li> 0 = Default
@@ -7339,8 +7339,8 @@
          * </ul>
          * @hide
          */
-        public static final String HEARING_AID_SYSTEM_SOUNDS_ROUTING =
-                "hearing_aid_system_sounds_routing";
+        public static final String HEARING_AID_NOTIFICATION_ROUTING =
+                "hearing_aid_notification_routing";
 
         /**
          * Setting to indicate that on device captions are enabled.
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 875031f..23c8393 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -960,8 +960,8 @@
                 mKeyphraseMetadata = new KeyphraseMetadata(1, mText, fakeSupportedLocales,
                         AlwaysOnHotwordDetector.RECOGNITION_MODE_VOICE_TRIGGER);
             }
+            notifyStateChangedLocked();
         }
-        notifyStateChanged(availability);
     }
 
     /**
@@ -1371,8 +1371,8 @@
 
             mAvailability = STATE_INVALID;
             mIsAvailabilityOverriddenByTestApi = false;
+            notifyStateChangedLocked();
         }
-        notifyStateChanged(STATE_INVALID);
         super.destroy();
     }
 
@@ -1402,8 +1402,6 @@
      */
     // TODO(b/281608561): remove the enrollment flow from AlwaysOnHotwordDetector
     void onSoundModelsChanged() {
-        boolean notifyError = false;
-
         synchronized (mLock) {
             if (mAvailability == STATE_INVALID
                     || mAvailability == STATE_HARDWARE_UNAVAILABLE
@@ -1444,9 +1442,6 @@
                     // calling stopRecognition where there is no started session.
                     Log.w(TAG, "Failed to stop recognition after enrollment update: code="
                             + result);
-
-                    // Execute a refresh availability task - which should then notify of a change.
-                    new RefreshAvailabilityTask().execute();
                 } catch (Exception e) {
                     Slog.w(TAG, "Failed to stop recognition after enrollment update", e);
                     if (CompatChanges.isChangeEnabled(SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS)) {
@@ -1455,14 +1450,14 @@
                                         + Log.getStackTraceString(e),
                                 FailureSuggestedAction.RECREATE_DETECTOR));
                     } else {
-                        notifyError = true;
+                        updateAndNotifyStateChangedLocked(STATE_ERROR);
                     }
+                    return;
                 }
             }
-        }
 
-        if (notifyError) {
-            updateAndNotifyStateChanged(STATE_ERROR);
+            // Execute a refresh availability task - which should then notify of a change.
+            new RefreshAvailabilityTask().execute();
         }
     }
 
@@ -1578,11 +1573,10 @@
         }
     }
 
-    private void updateAndNotifyStateChanged(int availability) {
-        synchronized (mLock) {
-            updateAvailabilityLocked(availability);
-        }
-        notifyStateChanged(availability);
+    @GuardedBy("mLock")
+    private void updateAndNotifyStateChangedLocked(int availability) {
+        updateAvailabilityLocked(availability);
+        notifyStateChangedLocked();
     }
 
     @GuardedBy("mLock")
@@ -1596,17 +1590,17 @@
         }
     }
 
-    private void notifyStateChanged(int newAvailability) {
+    @GuardedBy("mLock")
+    private void notifyStateChangedLocked() {
         Message message = Message.obtain(mHandler, MSG_AVAILABILITY_CHANGED);
-        message.arg1 = newAvailability;
+        message.arg1 = mAvailability;
         message.sendToTarget();
     }
 
+    @GuardedBy("mLock")
     private void sendUnknownFailure(String failureMessage) {
-        synchronized (mLock) {
-            // update but do not call onAvailabilityChanged callback for STATE_ERROR
-            updateAvailabilityLocked(STATE_ERROR);
-        }
+        // update but do not call onAvailabilityChanged callback for STATE_ERROR
+        updateAvailabilityLocked(STATE_ERROR);
         Message.obtain(mHandler, MSG_DETECTION_UNKNOWN_FAILURE, failureMessage).sendToTarget();
     }
 
@@ -1822,17 +1816,19 @@
                             availability = STATE_KEYPHRASE_UNENROLLED;
                         }
                     }
+                    updateAndNotifyStateChangedLocked(availability);
                 }
-                updateAndNotifyStateChanged(availability);
             } catch (Exception e) {
                 // Any exception here not caught will crash the process because AsyncTask does not
                 // bubble up the exceptions to the client app, so we must propagate it to the app.
                 Slog.w(TAG, "Failed to refresh availability", e);
-                if (CompatChanges.isChangeEnabled(SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS)) {
-                    sendUnknownFailure(
-                            "Failed to refresh availability: " + Log.getStackTraceString(e));
-                } else {
-                    updateAndNotifyStateChanged(STATE_ERROR);
+                synchronized (mLock) {
+                    if (CompatChanges.isChangeEnabled(SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS)) {
+                        sendUnknownFailure(
+                                "Failed to refresh availability: " + Log.getStackTraceString(e));
+                    } else {
+                        updateAndNotifyStateChangedLocked(STATE_ERROR);
+                    }
                 }
             }
 
diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java
index b7d9705..adc54f5 100644
--- a/core/java/android/service/voice/VisualQueryDetector.java
+++ b/core/java/android/service/voice/VisualQueryDetector.java
@@ -94,7 +94,9 @@
      */
     public void updateState(@Nullable PersistableBundle options,
             @Nullable SharedMemory sharedMemory) {
-        mInitializationDelegate.updateState(options, sharedMemory);
+        synchronized (mInitializationDelegate.getLock()) {
+            mInitializationDelegate.updateState(options, sharedMemory);
+        }
     }
 
 
@@ -116,18 +118,21 @@
         if (DEBUG) {
             Slog.i(TAG, "#startRecognition");
         }
-        // check if the detector is active with the initialization delegate
-        mInitializationDelegate.startRecognition();
+        synchronized (mInitializationDelegate.getLock()) {
+            // check if the detector is active with the initialization delegate
+            mInitializationDelegate.startRecognition();
 
-        try {
-            mManagerService.startPerceiving(new BinderCallback(mExecutor, mCallback));
-        } catch (SecurityException e) {
-            Slog.e(TAG, "startRecognition failed: " + e);
-            return false;
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
+            try {
+                mManagerService.startPerceiving(new BinderCallback(
+                        mExecutor, mCallback, mInitializationDelegate.getLock()));
+            } catch (SecurityException e) {
+                Slog.e(TAG, "startRecognition failed: " + e);
+                return false;
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+            return true;
         }
-        return true;
     }
 
     /**
@@ -140,15 +145,17 @@
         if (DEBUG) {
             Slog.i(TAG, "#stopRecognition");
         }
-        // check if the detector is active with the initialization delegate
-        mInitializationDelegate.startRecognition();
+        synchronized (mInitializationDelegate.getLock()) {
+            // check if the detector is active with the initialization delegate
+            mInitializationDelegate.stopRecognition();
 
-        try {
-            mManagerService.stopPerceiving();
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
+            try {
+                mManagerService.stopPerceiving();
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+            return true;
         }
-        return true;
     }
 
     /**
@@ -160,12 +167,16 @@
         if (DEBUG) {
             Slog.i(TAG, "#destroy");
         }
-        mInitializationDelegate.destroy();
+        synchronized (mInitializationDelegate.getLock()) {
+            mInitializationDelegate.destroy();
+        }
     }
 
     /** @hide */
     public void dump(String prefix, PrintWriter pw) {
-        // TODO: implement this
+        synchronized (mInitializationDelegate.getLock()) {
+            mInitializationDelegate.dump(prefix, pw);
+        }
     }
 
     /** @hide */
@@ -175,7 +186,9 @@
 
     /** @hide */
     void registerOnDestroyListener(Consumer<AbstractDetector> onDestroyListener) {
-        mInitializationDelegate.registerOnDestroyListener(onDestroyListener);
+        synchronized (mInitializationDelegate.getLock()) {
+            mInitializationDelegate.registerOnDestroyListener(onDestroyListener);
+        }
     }
 
     /**
@@ -282,6 +295,15 @@
         public boolean isUsingSandboxedDetectionService() {
             return true;
         }
+
+        @Override
+        public void dump(String prefix, PrintWriter pw) {
+            // No-op
+        }
+
+        private Object getLock() {
+            return mLock;
+        }
     }
 
     private static class BinderCallback
@@ -289,31 +311,43 @@
         private final Executor mExecutor;
         private final VisualQueryDetector.Callback mCallback;
 
-        BinderCallback(Executor executor, VisualQueryDetector.Callback callback) {
+        private final Object mLock;
+
+        BinderCallback(Executor executor, VisualQueryDetector.Callback callback, Object lock) {
             this.mExecutor = executor;
             this.mCallback = callback;
+            this.mLock = lock;
         }
 
         /** Called when the detected result is valid. */
         @Override
         public void onQueryDetected(@NonNull String partialQuery) {
             Slog.v(TAG, "BinderCallback#onQueryDetected");
-            Binder.withCleanCallingIdentity(() -> mExecutor.execute(
-                    () -> mCallback.onQueryDetected(partialQuery)));
+            Binder.withCleanCallingIdentity(() -> {
+                synchronized (mLock) {
+                    mCallback.onQueryDetected(partialQuery);
+                }
+            });
         }
 
         @Override
         public void onQueryFinished() {
             Slog.v(TAG, "BinderCallback#onQueryFinished");
-            Binder.withCleanCallingIdentity(() -> mExecutor.execute(
-                    () -> mCallback.onQueryFinished()));
+            Binder.withCleanCallingIdentity(() -> {
+                synchronized (mLock) {
+                    mCallback.onQueryFinished();
+                }
+            });
         }
 
         @Override
         public void onQueryRejected() {
             Slog.v(TAG, "BinderCallback#onQueryRejected");
-            Binder.withCleanCallingIdentity(() -> mExecutor.execute(
-                    () -> mCallback.onQueryRejected()));
+            Binder.withCleanCallingIdentity(() -> {
+                synchronized (mLock) {
+                    mCallback.onQueryRejected();
+                }
+            });
         }
 
         /** Called when the detection fails due to an error. */
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index 31d759e..18080e4 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -287,6 +287,16 @@
     }
 
     /**
+     * Called when a display hdcp levels change event is received.
+     *
+     * @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
+     * @param connectedLevel the new connected HDCP level
+     * @param maxLevel the maximum HDCP level
+     */
+    public void onHdcpLevelsChanged(long physicalDisplayId, int connectedLevel, int maxLevel) {
+    }
+
+    /**
      * Represents a mapping between a UID and an override frame rate
      */
     public static class FrameRateOverride {
@@ -374,4 +384,11 @@
         onFrameRateOverridesChanged(timestampNanos, physicalDisplayId, overrides);
     }
 
+    // Called from native code.
+    @SuppressWarnings("unused")
+    private void dispatchHdcpLevelsChanged(long physicalDisplayId, int connectedLevel,
+            int maxLevel) {
+        onHdcpLevelsChanged(physicalDisplayId, connectedLevel, maxLevel);
+    }
+
 }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e83488e..9f6395e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -528,8 +528,6 @@
     // Set to true to stop input during an Activity Transition.
     boolean mPausedForTransition = false;
 
-    boolean mLastInCompatMode = false;
-
     SurfaceHolder.Callback2 mSurfaceHolderCallback;
     BaseSurfaceHolder mSurfaceHolder;
     boolean mIsCreating;
@@ -1375,11 +1373,6 @@
                 }
                 if (DEBUG_LAYOUT) Log.d(mTag, "WindowLayout in setView:" + attrs);
 
-                if (!compatibilityInfo.supportsScreen()) {
-                    attrs.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
-                    mLastInCompatMode = true;
-                }
-
                 mSoftInputMode = attrs.softInputMode;
                 mWindowAttributesChanged = true;
                 mAttachInfo.mRootView = view;
@@ -1913,10 +1906,6 @@
             // Keep track of the actual window flags supplied by the client.
             mClientWindowLayoutFlags = attrs.flags;
 
-            // Preserve compatible window flag if exists.
-            final int compatibleWindowFlag = mWindowAttributes.privateFlags
-                    & WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
-
             // Preserve system UI visibility.
             final int systemUiVisibility = mWindowAttributes.systemUiVisibility;
             final int subtreeSystemUiVisibility = mWindowAttributes.subtreeSystemUiVisibility;
@@ -1948,8 +1937,7 @@
             mWindowAttributes.subtreeSystemUiVisibility = subtreeSystemUiVisibility;
             mWindowAttributes.insetsFlags.appearance = appearance;
             mWindowAttributes.insetsFlags.behavior = behavior;
-            mWindowAttributes.privateFlags |= compatibleWindowFlag
-                    | appearanceAndBehaviorPrivateFlags;
+            mWindowAttributes.privateFlags |= appearanceAndBehaviorPrivateFlags;
 
             if (mWindowAttributes.preservePreviousSurfaceInsets) {
                 // Restore old surface insets.
@@ -3150,21 +3138,6 @@
         final boolean shouldOptimizeMeasure = shouldOptimizeMeasure(lp);
 
         WindowManager.LayoutParams params = null;
-        CompatibilityInfo compatibilityInfo =
-                mDisplay.getDisplayAdjustments().getCompatibilityInfo();
-        if (compatibilityInfo.supportsScreen() == mLastInCompatMode) {
-            params = lp;
-            mFullRedrawNeeded = true;
-            mLayoutRequested = true;
-            if (mLastInCompatMode) {
-                params.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
-                mLastInCompatMode = false;
-            } else {
-                params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
-                mLastInCompatMode = true;
-            }
-        }
-
         Rect frame = mWinFrame;
         if (mFirst) {
             mFullRedrawNeeded = true;
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index e3d9c60..07a347a 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -122,7 +122,6 @@
 import android.view.WindowInsets.Type;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.accessibility.AccessibilityNodeInfo;
-import android.window.ITrustedPresentationListener;
 import android.window.TaskFpsCallback;
 import android.window.TrustedPresentationThresholds;
 
@@ -1280,9 +1279,8 @@
      *     android:value="true|false"/&gt;
      * &lt;/application&gt;
      * </pre>
-     * @hide
      */
-    // TODO(b/279428317): Make this public API.
+    @FlaggedApi(Flags.FLAG_APP_COMPAT_PROPERTIES_API)
     String PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE =
             "android.window.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE";
 
@@ -3145,13 +3143,6 @@
         @UnsupportedAppUsage
         public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 1 << 6;
 
-        /** Window flag: special flag to limit the size of the window to be
-         * original size ([320x480] x density). Used to create window for applications
-         * running under compatibility mode.
-         *
-         * {@hide} */
-        public static final int PRIVATE_FLAG_COMPATIBLE_WINDOW = 1 << 7;
-
         /** Window flag: a special option intended for system dialogs.  When
          * this flag is set, the window will demand focus unconditionally when
          * it is created.
@@ -3347,7 +3338,6 @@
                 SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
                 PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION,
                 PRIVATE_FLAG_NO_MOVE_ANIMATION,
-                PRIVATE_FLAG_COMPATIBLE_WINDOW,
                 PRIVATE_FLAG_SYSTEM_ERROR,
                 PRIVATE_FLAG_OPTIMIZE_MEASURE,
                 PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
@@ -3401,10 +3391,6 @@
                         equals = PRIVATE_FLAG_NO_MOVE_ANIMATION,
                         name = "NO_MOVE_ANIMATION"),
                 @ViewDebug.FlagToString(
-                        mask = PRIVATE_FLAG_COMPATIBLE_WINDOW,
-                        equals = PRIVATE_FLAG_COMPATIBLE_WINDOW,
-                        name = "COMPATIBLE_WINDOW"),
-                @ViewDebug.FlagToString(
                         mask = PRIVATE_FLAG_SYSTEM_ERROR,
                         equals = PRIVATE_FLAG_SYSTEM_ERROR,
                         name = "SYSTEM_ERROR"),
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 966161f..19ba316 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -45,6 +45,7 @@
 import android.os.IBinder.DeathRecipient;
 import android.os.RemoteException;
 import android.os.Trace;
+import android.service.contentcapture.ContentCaptureService;
 import android.text.Selection;
 import android.text.Spannable;
 import android.text.TextUtils;
@@ -69,6 +70,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.NoSuchElementException;
+import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -152,7 +154,16 @@
     public ComponentName mComponentName;
 
     /**
-     * List of events held to be sent as a batch.
+     * Thread-safe queue of events held to be processed as a batch.
+     *
+     * Because it is not guaranteed that the events will be enqueued from a single thread, the
+     * implementation must be thread-safe to prevent unexpected behaviour.
+     */
+    @NonNull
+    private final ConcurrentLinkedQueue<ContentCaptureEvent> mEventProcessQueue;
+
+    /**
+     * List of events held to be sent to the {@link ContentCaptureService} as a batch.
      *
      * @hide
      */
@@ -238,6 +249,8 @@
         mFlushHistory = logHistorySize > 0 ? new LocalLog(logHistorySize) : null;
 
         mSessionStateReceiver = new SessionStateReceiver(this);
+
+        mEventProcessQueue = new ConcurrentLinkedQueue<>();
     }
 
     @Override
@@ -733,6 +746,9 @@
         }
         mDirectServiceInterface = null;
         mContentProtectionEventProcessor = null;
+        if (runOnBackgroundThreadEnabled()) {
+            mEventProcessQueue.clear();
+        }
     }
 
     // TODO(b/122454205): once we support multiple sessions, we might need to move some of these
@@ -823,27 +839,30 @@
     // change should also get get rid of the "internalNotifyXXXX" methods above
     void notifyChildSessionStarted(int parentSessionId, int childSessionId,
             @NonNull ContentCaptureContext clientContext) {
-        runOnContentCaptureThread(
-                () -> sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED)
-                .setParentSessionId(parentSessionId).setClientContext(clientContext),
-                FORCE_FLUSH));
+        final ContentCaptureEvent event =
+                new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED)
+                        .setParentSessionId(parentSessionId)
+                        .setClientContext(clientContext);
+        enqueueEvent(event, FORCE_FLUSH);
     }
 
     void notifyChildSessionFinished(int parentSessionId, int childSessionId) {
-        runOnContentCaptureThread(
-                () -> sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED)
-                .setParentSessionId(parentSessionId), FORCE_FLUSH));
+        final ContentCaptureEvent event =
+                new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED)
+                        .setParentSessionId(parentSessionId);
+        enqueueEvent(event, FORCE_FLUSH);
     }
 
     void notifyViewAppeared(int sessionId, @NonNull ViewStructureImpl node) {
-        runOnContentCaptureThread(() ->
-                sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED)
-                .setViewNode(node.mNode)));
+        final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED)
+                .setViewNode(node.mNode);
+        enqueueEvent(event);
     }
 
     void notifyViewDisappeared(int sessionId, @NonNull AutofillId id) {
-        runOnContentCaptureThread(() -> sendEvent(
-                new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED).setAutofillId(id)));
+        final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED)
+                .setAutofillId(id);
+        enqueueEvent(event);
     }
 
     void notifyViewTextChanged(int sessionId, @NonNull AutofillId id, @Nullable CharSequence text) {
@@ -867,50 +886,90 @@
 
         final int startIndex = Selection.getSelectionStart(text);
         final int endIndex = Selection.getSelectionEnd(text);
-        runOnContentCaptureThread(() -> sendEvent(
-                new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED)
-                        .setAutofillId(id).setText(eventText)
-                        .setComposingIndex(composingStart, composingEnd)
-                        .setSelectionIndex(startIndex, endIndex)));
+
+        final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED)
+                .setAutofillId(id).setText(eventText)
+                .setComposingIndex(composingStart, composingEnd)
+                .setSelectionIndex(startIndex, endIndex);
+        enqueueEvent(event);
     }
 
     void notifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets) {
-        runOnContentCaptureThread(() ->
-                sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_INSETS_CHANGED)
-                .setInsets(viewInsets)));
+        final ContentCaptureEvent event =
+                new ContentCaptureEvent(sessionId, TYPE_VIEW_INSETS_CHANGED)
+                        .setInsets(viewInsets);
+        enqueueEvent(event);
     }
 
     void notifyViewTreeEvent(int sessionId, boolean started) {
         final int type = started ? TYPE_VIEW_TREE_APPEARING : TYPE_VIEW_TREE_APPEARED;
         final boolean disableFlush = mManager.getFlushViewTreeAppearingEventDisabled();
+        final boolean forceFlush = disableFlush ? !started : FORCE_FLUSH;
 
-        runOnContentCaptureThread(() -> sendEvent(
-                new ContentCaptureEvent(sessionId, type),
-                disableFlush ? !started : FORCE_FLUSH));
+        final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, type);
+        enqueueEvent(event, forceFlush);
     }
 
     void notifySessionResumed(int sessionId) {
-        runOnContentCaptureThread(() -> sendEvent(
-                new ContentCaptureEvent(sessionId, TYPE_SESSION_RESUMED), FORCE_FLUSH));
+        final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_SESSION_RESUMED);
+        enqueueEvent(event, FORCE_FLUSH);
     }
 
     void notifySessionPaused(int sessionId) {
-        runOnContentCaptureThread(() -> sendEvent(
-                new ContentCaptureEvent(sessionId, TYPE_SESSION_PAUSED), FORCE_FLUSH));
+        final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_SESSION_PAUSED);
+        enqueueEvent(event, FORCE_FLUSH);
     }
 
     void notifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) {
-        runOnContentCaptureThread(() ->
-                sendEvent(new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED)
-                .setClientContext(context), FORCE_FLUSH));
+        final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED)
+                .setClientContext(context);
+        enqueueEvent(event, FORCE_FLUSH);
     }
 
     /** public because is also used by ViewRootImpl */
     public void notifyWindowBoundsChanged(int sessionId, @NonNull Rect bounds) {
-        runOnContentCaptureThread(() -> sendEvent(
+        final ContentCaptureEvent event =
                 new ContentCaptureEvent(sessionId, TYPE_WINDOW_BOUNDS_CHANGED)
-                .setBounds(bounds)
-        ));
+                        .setBounds(bounds);
+        enqueueEvent(event);
+    }
+
+    private List<ContentCaptureEvent> clearBufferEvents() {
+        final ArrayList<ContentCaptureEvent> bufferEvents = new ArrayList<>();
+        ContentCaptureEvent event;
+        while ((event = mEventProcessQueue.poll()) != null) {
+            bufferEvents.add(event);
+        }
+        return bufferEvents;
+    }
+
+    private void enqueueEvent(@NonNull final ContentCaptureEvent event) {
+        enqueueEvent(event, /* forceFlush */ false);
+    }
+
+    /**
+     * Enqueue the event into {@code mEventProcessBuffer} if it is not an urgent request. Otherwise,
+     * clear the buffer events then starting sending out current event.
+     */
+    private void enqueueEvent(@NonNull final ContentCaptureEvent event, boolean forceFlush) {
+        if (runOnBackgroundThreadEnabled()) {
+            if (forceFlush) {
+                // The buffer events are cleared in the same thread first to prevent new events
+                // being added during the time of context switch. This would disrupt the sequence
+                // of events.
+                final List<ContentCaptureEvent> batchEvents = clearBufferEvents();
+                runOnContentCaptureThread(() -> {
+                    for (int i = 0; i < batchEvents.size(); i++) {
+                        sendEvent(batchEvents.get(i));
+                    }
+                    sendEvent(event, /* forceFlush= */ true);
+                });
+            } else {
+                mEventProcessQueue.offer(event);
+            }
+        } else {
+            mHandler.post(() -> sendEvent(event, forceFlush));
+        }
     }
 
     /** public because is also used by ViewRootImpl */
diff --git a/core/java/android/window/BackMotionEvent.java b/core/java/android/window/BackMotionEvent.java
index c475723..7cda3a3 100644
--- a/core/java/android/window/BackMotionEvent.java
+++ b/core/java/android/window/BackMotionEvent.java
@@ -36,6 +36,7 @@
     private final float mProgress;
     private final float mVelocityX;
     private final float mVelocityY;
+    private final boolean mTriggerBack;
 
     @BackEvent.SwipeEdge
     private final int mSwipeEdge;
@@ -54,6 +55,7 @@
      *                  Value in pixels/second. {@link Float#NaN} if was not computed.
      * @param velocityY Y velocity computed from the touch point of this event.
      *                  Value in pixels/second. {@link Float#NaN} if was not computed.
+     * @param triggerBack Indicates whether the back arrow is in the triggered state or not
      * @param swipeEdge Indicates which edge the swipe starts from.
      * @param departingAnimationTarget The remote animation target of the departing
      *                                 application window.
@@ -64,6 +66,7 @@
             float progress,
             float velocityX,
             float velocityY,
+            boolean triggerBack,
             @BackEvent.SwipeEdge int swipeEdge,
             @Nullable RemoteAnimationTarget departingAnimationTarget) {
         mTouchX = touchX;
@@ -71,6 +74,7 @@
         mProgress = progress;
         mVelocityX = velocityX;
         mVelocityY = velocityY;
+        mTriggerBack = triggerBack;
         mSwipeEdge = swipeEdge;
         mDepartingAnimationTarget = departingAnimationTarget;
     }
@@ -81,6 +85,7 @@
         mProgress = in.readFloat();
         mVelocityX = in.readFloat();
         mVelocityY = in.readFloat();
+        mTriggerBack = in.readBoolean();
         mSwipeEdge = in.readInt();
         mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR);
     }
@@ -110,6 +115,7 @@
         dest.writeFloat(mProgress);
         dest.writeFloat(mVelocityX);
         dest.writeFloat(mVelocityY);
+        dest.writeBoolean(mTriggerBack);
         dest.writeInt(mSwipeEdge);
         dest.writeTypedObject(mDepartingAnimationTarget, flags);
     }
@@ -157,6 +163,15 @@
     }
 
     /**
+     * Returns whether the back arrow is in the triggered state or not
+     *
+     * @return boolean indicating whether the back arrow is in the triggered state or not
+     */
+    public boolean getTriggerBack() {
+        return mTriggerBack;
+    }
+
+    /**
      * Returns the screen edge that the swipe starts from.
      */
     @BackEvent.SwipeEdge
@@ -182,6 +197,7 @@
                 + ", mProgress=" + mProgress
                 + ", mVelocityX=" + mVelocityX
                 + ", mVelocityY=" + mVelocityY
+                + ", mTriggerBack=" + mTriggerBack
                 + ", mSwipeEdge" + mSwipeEdge
                 + ", mDepartingAnimationTarget" + mDepartingAnimationTarget
                 + "}";
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 727bff4..4b5595f 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
@@ -23,3 +23,23 @@
   bug: "297550533"
   is_fixed_read_only: true
 }
+
+flag {
+  name: "app_compat_properties_api"
+  namespace: "large_screen_experiences_app_compat"
+  description: "Whether app compat property APIs are public. Which includes: /n"
+                 "WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE,/n"
+                 "WindowManager.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED,/n"
+                 "WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES,/n"
+                 "WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE,/n"
+                 "WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE"
+  bug: "316139088"
+  is_fixed_read_only: true
+}
+
+flag {
+  name: "user_min_aspect_ratio_app_default"
+  namespace: "large_screen_experiences_app_compat"
+  description: "Whether the API PackageManager.USER_MIN_ASPECT_RATIO_APP_DEFAULT is available"
+  bug: "310816437"
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 07beb11..216acdc 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -15,6 +15,14 @@
 }
 
 flag {
+  name: "enforce_edge_to_edge"
+  namespace: "windowing_frontend"
+  description: "Make app go edge-to-edge when targeting SDK level 35 or greater"
+  bug: "309578419"
+  is_fixed_read_only: true
+}
+
+flag {
     name: "defer_display_updates"
     namespace: "windowing_frontend"
     description: "Feature flag for deferring DisplayManager updates to WindowManager if Shell transition is running"
@@ -58,4 +66,12 @@
     description: "Predictive back for system animations"
     bug: "309545085"
     is_fixed_read_only: true
+}
+
+flag {
+    name: "activity_snapshot_by_default"
+    namespace: "systemui"
+    description: "Enable record activity snapshot by default"
+    bug: "259497289"
+    is_fixed_read_only: true
 }
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 7e2c017..86d3037 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -24,7 +24,9 @@
 import static android.content.ContentProvider.getUserIdFromUri;
 import static android.stats.devicepolicy.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL;
 import static android.stats.devicepolicy.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK;
+
 import static com.android.internal.util.LatencyTracker.ACTION_LOAD_SHARE_SHEET;
+
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import android.animation.Animator;
@@ -2868,7 +2870,6 @@
     @Override
     public void onListRebuilt(ResolverListAdapter listAdapter, boolean rebuildComplete) {
         setupScrollListener();
-        maybeSetupGlobalLayoutListener();
 
         ChooserListAdapter chooserListAdapter = (ChooserListAdapter) listAdapter;
         if (chooserListAdapter.getUserHandle()
@@ -2970,28 +2971,6 @@
                 });
     }
 
-    private void maybeSetupGlobalLayoutListener() {
-        if (shouldShowTabs()) {
-            return;
-        }
-        final View recyclerView = mChooserMultiProfilePagerAdapter.getActiveAdapterView();
-        recyclerView.getViewTreeObserver()
-                .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
-                    @Override
-                    public void onGlobalLayout() {
-                        // Fixes an issue were the accessibility border disappears on list creation.
-                        recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
-                        final TextView titleView = findViewById(R.id.title);
-                        if (titleView != null) {
-                            titleView.setFocusable(true);
-                            titleView.setFocusableInTouchMode(true);
-                            titleView.requestFocus();
-                            titleView.requestAccessibilityFocus();
-                        }
-                    }
-                });
-    }
-
     @Override // ChooserListCommunicator
     public boolean isSendAction(Intent targetIntent) {
         if (targetIntent == null) {
diff --git a/core/java/com/android/internal/app/SuspendedAppActivity.java b/core/java/com/android/internal/app/SuspendedAppActivity.java
index efc1455..467cd49 100644
--- a/core/java/com/android/internal/app/SuspendedAppActivity.java
+++ b/core/java/com/android/internal/app/SuspendedAppActivity.java
@@ -39,6 +39,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.SuspendDialogInfo;
+import android.content.pm.UserPackage;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
@@ -308,7 +309,8 @@
                         try {
                             final String[] errored = ipm.setPackagesSuspendedAsUser(
                                     new String[]{mSuspendedPackage}, false, null, null, null, 0,
-                                    mSuspendingPackage, mUserId);
+                                    mSuspendingPackage, mUserId /* suspendingUserId */,
+                                    mUserId /* targetUserId */);
                             if (ArrayUtils.contains(errored, mSuspendedPackage)) {
                                 Slog.e(TAG, "Could not unsuspend " + mSuspendedPackage);
                                 break;
@@ -350,17 +352,19 @@
     }
 
     public static Intent createSuspendedAppInterceptIntent(String suspendedPackage,
-            String suspendingPackage, SuspendDialogInfo dialogInfo, Bundle options,
+            UserPackage suspendingPackage, SuspendDialogInfo dialogInfo, Bundle options,
             IntentSender onUnsuspend, int userId) {
-        return new Intent()
+        Intent intent = new Intent()
                 .setClassName("android", SuspendedAppActivity.class.getName())
                 .putExtra(EXTRA_SUSPENDED_PACKAGE, suspendedPackage)
                 .putExtra(EXTRA_DIALOG_INFO, dialogInfo)
-                .putExtra(EXTRA_SUSPENDING_PACKAGE, suspendingPackage)
+                .putExtra(EXTRA_SUSPENDING_PACKAGE,
+                        suspendingPackage != null ? suspendingPackage.packageName : null)
                 .putExtra(EXTRA_UNSUSPEND_INTENT, onUnsuspend)
                 .putExtra(EXTRA_ACTIVITY_OPTIONS, options)
                 .putExtra(Intent.EXTRA_USER_ID, userId)
                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                         | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+        return intent;
     }
 }
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 8b1879f..0ec8b74 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -117,8 +117,6 @@
     @Deprecated public static final int CUJ_NOTIFICATION_SHADE_ROW_SWIPE = Cuj.CUJ_NOTIFICATION_SHADE_ROW_SWIPE;
     @Deprecated public static final int CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
     @Deprecated public static final int CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE = Cuj.CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE;
-    @Deprecated public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME = Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME;
-    @Deprecated public static final int CUJ_LAUNCHER_QUICK_SWITCH = Cuj.CUJ_LAUNCHER_QUICK_SWITCH;
     @Deprecated public static final int CUJ_NOTIFICATION_HEADS_UP_APPEAR = Cuj.CUJ_NOTIFICATION_HEADS_UP_APPEAR;
     @Deprecated public static final int CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR = Cuj.CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR;
     @Deprecated public static final int CUJ_NOTIFICATION_ADD = Cuj.CUJ_NOTIFICATION_ADD;
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 201b23c..31910ac 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -169,20 +169,12 @@
     private final static int DEFAULT_BACKGROUND_FADE_DURATION_MS = 300;
 
     /**
-     * Makes navigation bar color transparent by default if the target SDK is
-     * {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or above.
-     */
-    @ChangeId
-    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
-    private static final long NAV_BAR_COLOR_DEFAULT_TRANSPARENT = 232195501L;
-
-    /**
      * Make app go edge-to-edge by default if the target SDK is
      * {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or above.
      */
     @ChangeId
     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
-    private static final long EDGE_TO_EDGE_BY_DEFAULT = 309578419;
+    private static final long ENFORCE_EDGE_TO_EDGE = 309578419;
 
     private static final int CUSTOM_TITLE_COMPATIBLE_FEATURES = DEFAULT_FEATURES |
             (1 << FEATURE_CUSTOM_TITLE) |
@@ -193,9 +185,9 @@
     private static final Transition USE_DEFAULT_TRANSITION = new TransitionSet();
 
     /**
-     * Since which target SDK version this window should be edge-to-edge by default.
+     * Since which target SDK version this window is enforced to go edge-to-edge.
      */
-    private static final int DEFAULT_EDGE_TO_EDGE_SDK_VERSION =
+    private static final int ENFORCE_EDGE_TO_EDGE_SDK_VERSION =
             SystemProperties.getInt("persist.wm.debug.default_e2e_since_sdk", Integer.MAX_VALUE);
 
     /**
@@ -376,7 +368,7 @@
     boolean mDecorFitsSystemWindows = true;
 
     @VisibleForTesting
-    public final boolean mDefaultEdgeToEdge;
+    public final boolean mEdgeToEdgeEnforced;
 
     private final ProxyOnBackInvokedDispatcher mProxyOnBackInvokedDispatcher;
 
@@ -396,11 +388,11 @@
         mProxyOnBackInvokedDispatcher = new ProxyOnBackInvokedDispatcher(context);
         mAllowFloatingWindowsFillScreen = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_allowFloatingWindowsFillScreen);
-        mDefaultEdgeToEdge =
-                context.getApplicationInfo().targetSdkVersion >= DEFAULT_EDGE_TO_EDGE_SDK_VERSION
-                        || (CompatChanges.isChangeEnabled(EDGE_TO_EDGE_BY_DEFAULT)
-                                && Flags.edgeToEdgeByDefault());
-        if (mDefaultEdgeToEdge) {
+        mEdgeToEdgeEnforced =
+                context.getApplicationInfo().targetSdkVersion >= ENFORCE_EDGE_TO_EDGE_SDK_VERSION
+                        || (CompatChanges.isChangeEnabled(ENFORCE_EDGE_TO_EDGE)
+                                && Flags.enforceEdgeToEdge());
+        if (mEdgeToEdgeEnforced) {
             mDecorFitsSystemWindows = false;
         }
     }
@@ -2472,7 +2464,7 @@
             setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
             params.setFitInsetsSides(0);
             params.setFitInsetsTypes(0);
-            if (mDefaultEdgeToEdge) {
+            if (mEdgeToEdgeEnforced) {
                 params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
             }
         }
@@ -2562,7 +2554,7 @@
             final int statusBarColor = a.getColor(R.styleable.Window_statusBarColor,
                     statusBarDefaultColor);
 
-            mStatusBarColor = statusBarColor == statusBarDefaultColor && !mDefaultEdgeToEdge
+            mStatusBarColor = statusBarColor == statusBarDefaultColor && !mEdgeToEdgeEnforced
                     ? statusBarCompatibleColor
                     : statusBarColor;
         }
@@ -2574,9 +2566,7 @@
 
             mNavigationBarColor =
                     navBarColor == navBarDefaultColor
-                            && !mDefaultEdgeToEdge
-                            && !(CompatChanges.isChangeEnabled(NAV_BAR_COLOR_DEFAULT_TRANSPARENT)
-                                    && Flags.navBarTransparentByDefault())
+                            && !mEdgeToEdgeEnforced
                             && !context.getResources().getBoolean(
                                     R.bool.config_navBarDefaultTransparent)
                     ? navBarCompatibleColor
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index c24d21d..17aad43 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -653,6 +653,8 @@
             sizeof("-Xps-save-resolved-classes-delay-ms:")-1 + PROPERTY_VALUE_MAX];
     char profileMinSavePeriodOptsBuf[sizeof("-Xps-min-save-period-ms:")-1 + PROPERTY_VALUE_MAX];
     char profileMinFirstSaveOptsBuf[sizeof("-Xps-min-first-save-ms:") - 1 + PROPERTY_VALUE_MAX];
+    char profileInlineCacheThresholdOptsBuf[
+            sizeof("-Xps-inline-cache-threshold:") - 1 + PROPERTY_VALUE_MAX];
     char madviseWillNeedFileSizeVdex[
             sizeof("-XMadviseWillNeedVdexFileSize:")-1 + PROPERTY_VALUE_MAX];
     char madviseWillNeedFileSizeOdex[
@@ -898,6 +900,9 @@
     parseRuntimeOption("dalvik.vm.ps-min-first-save-ms", profileMinFirstSaveOptsBuf,
             "-Xps-min-first-save-ms:");
 
+    parseRuntimeOption("dalvik.vm.ps-inline-cache-threshold", profileInlineCacheThresholdOptsBuf,
+            "-Xps-inline-cache-threshold:");
+
     property_get("ro.config.low_ram", propBuf, "");
     if (strcmp(propBuf, "true") == 0) {
       addOption("-XX:LowMemoryMode");
diff --git a/core/jni/android_os_HidlSupport.cpp b/core/jni/android_os_HidlSupport.cpp
index e3602d8..3e51e93 100644
--- a/core/jni/android_os_HidlSupport.cpp
+++ b/core/jni/android_os_HidlSupport.cpp
@@ -15,6 +15,7 @@
  */
 
 #include <hidl/HidlTransportSupport.h>
+#include <hidl/ServiceManagement.h>
 #include <nativehelper/JNIHelp.h>
 
 #include "core_jni_helpers.h"
@@ -24,8 +25,13 @@
     return android::hardware::details::getPidIfSharable();
 }
 
+static jboolean android_os_HidlSupport_isHidlSupported(JNIEnv*, jclass) {
+    return android::hardware::isHidlSupported();
+}
+
 static const JNINativeMethod gHidlSupportMethods[] = {
-    {"getPidIfSharable", "()I", (void*)android_os_HidlSupport_getPidIfSharable},
+        {"getPidIfSharable", "()I", (void*)android_os_HidlSupport_getPidIfSharable},
+        {"isHidlSupported", "()Z", (void*)android_os_HidlSupport_isHidlSupported},
 };
 
 const char* const kHidlSupportPathName = "android/os/HidlSupport";
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index fef8ad7..f007cc5 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -41,6 +41,7 @@
     jmethodID dispatchHotplugConnectionError;
     jmethodID dispatchModeChanged;
     jmethodID dispatchFrameRateOverrides;
+    jmethodID dispatchHdcpLevelsChanged;
 
     struct {
         jclass clazz;
@@ -96,6 +97,8 @@
     void dispatchFrameRateOverrides(nsecs_t timestamp, PhysicalDisplayId displayId,
                                     std::vector<FrameRateOverride> overrides) override;
     void dispatchNullEvent(nsecs_t timestamp, PhysicalDisplayId displayId) override {}
+    void dispatchHdcpLevelsChanged(PhysicalDisplayId displayId, int connectedLevel,
+                                   int maxLevel) override;
 };
 
 NativeDisplayEventReceiver::NativeDisplayEventReceiver(JNIEnv* env, jobject receiverWeak,
@@ -294,6 +297,22 @@
     mMessageQueue->raiseAndClearException(env, "dispatchModeChanged");
 }
 
+void NativeDisplayEventReceiver::dispatchHdcpLevelsChanged(PhysicalDisplayId displayId,
+                                                           int connectedLevel, int maxLevel) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+
+    ScopedLocalRef<jobject> receiverObj(env, GetReferent(env, mReceiverWeakGlobal));
+    if (receiverObj.get()) {
+        ALOGV("receiver %p ~ Invoking hdcp levels changed handler.", this);
+        env->CallVoidMethod(receiverObj.get(),
+                            gDisplayEventReceiverClassInfo.dispatchHdcpLevelsChanged,
+                            displayId.value, connectedLevel, maxLevel);
+        ALOGV("receiver %p ~ Returned from hdcp levels changed handler.", this);
+    }
+
+    mMessageQueue->raiseAndClearException(env, "dispatchHdcpLevelsChanged");
+}
+
 static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, jobject vsyncEventDataWeak,
                         jobject messageQueueObj, jint vsyncSource, jint eventRegistration,
                         jlong layerHandle) {
@@ -385,6 +404,9 @@
             GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz,
                              "dispatchFrameRateOverrides",
                              "(JJ[Landroid/view/DisplayEventReceiver$FrameRateOverride;)V");
+    gDisplayEventReceiverClassInfo.dispatchHdcpLevelsChanged =
+            GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchHdcpLevelsChanged",
+                             "(JII)V");
 
     jclass frameRateOverrideClazz =
             FindClassOrDie(env, "android/view/DisplayEventReceiver$FrameRateOverride");
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 9ca1849..104c023 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -93,7 +93,7 @@
         optional SettingProto hearing_aid_ringtone_routing = 46 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto hearing_aid_call_routing = 47 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto hearing_aid_media_routing = 48 [ (android.privacy).dest = DEST_AUTOMATIC ];
-        optional SettingProto hearing_aid_system_sounds_routing = 49 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        optional SettingProto hearing_aid_notification_routing = 49 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto accessibility_magnification_joystick_enabled = 50 [ (android.privacy).dest = DEST_AUTOMATIC ];
         // Settings for font scaling
         optional SettingProto accessibility_font_scaling_has_been_changed = 51 [ (android.privacy).dest = DEST_AUTOMATIC ];
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index c5889ba..75cfba0 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -429,6 +429,7 @@
         optional string base_dir = 1;
         optional string res_dir = 2;
         optional string data_dir = 3;
+        optional int32 targetSdkVersion = 4;
     }
     optional AppInfo appinfo = 8;
     optional ProcessRecordProto app = 9;
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 404fa39..382a82c 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -394,6 +394,7 @@
     optional bool should_force_rotate_for_camera_compat = 39;
     optional bool should_refresh_activity_for_camera_compat = 40;
     optional bool should_refresh_activity_via_pause_for_camera_compat = 41;
+    optional bool should_override_min_aspect_ratio = 42;
 }
 
 /* represents WindowToken */
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 454eaee..c6209dd 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5189,7 +5189,9 @@
          this permission, it must hold the permission and be the active VoiceInteractionService in
          the system.
          {@see Settings.Secure.VOICE_INTERACTION_SERVICE}
-         @hide -->
+         @hide @SystemApi Intended for OEM and system apps.
+         <p>Protection level: signature|privileged
+    -->
     <permission android:name="android.permission.MANAGE_VOICE_KEYPHRASES"
         android:protectionLevel="signature|privileged" />
 
@@ -5198,7 +5200,9 @@
          sound models at any time. This permission should be reserved for system enrollment
          applications detected by {@link android.hardware.soundtrigger.KeyphraseEnrollmentInfo}
          only.
-         @hide <p>Not for use by third-party applications.</p> -->
+         @hide @SystemApi Intended for OEM and system apps.
+         <p>Protection level: signature|privileged
+    -->
     <permission android:name="android.permission.KEYPHRASE_ENROLLMENT_APPLICATION"
         android:protectionLevel="signature|privileged" />
 
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 1a3ec27..c058174 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -202,12 +202,14 @@
         "testng",
     ],
     srcs: [
+        "src/android/database/CursorWindowTest.java",
         "src/android/os/**/*.java",
-        "src/com/android/internal/os/**/*.java",
         "src/android/util/**/*.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",
+
         ":FrameworksCoreTests{.aapt.srcjar}",
         ":FrameworksCoreTests-aidl",
         ":FrameworksCoreTests-helpers",
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
index ba6c8fa..8308e7c 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
@@ -16,24 +16,35 @@
 
 package android.content.res
 
-
 import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.CheckFlagsRule
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
 import androidx.core.util.forEach
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Rule
 import kotlin.math.ceil
 import kotlin.math.floor
 import org.junit.Test
 import org.junit.runner.RunWith
+import java.lang.IllegalStateException
 import kotlin.random.Random.Default.nextFloat
 
+/**
+ * Unit tests for FontScaleConverterFactory. Note that some similar tests are in
+ * cts/tests/tests/content/src/android/content/res/cts/FontScaleConverterFactoryTest.kt
+ */
 @Presubmit
 @RunWith(AndroidJUnit4::class)
 class FontScaleConverterFactoryTest {
 
+    @get:Rule
+    val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
     @Test
     fun scale200IsTwiceAtSmallSizes() {
         val table = FontScaleConverterFactory.forScale(2F)!!
@@ -61,19 +72,6 @@
         assertThat(table.convertSpToDp(100F)).isWithin(CONVERSION_TOLERANCE).of(300f)
     }
 
-    @SmallTest
-    fun missingLookupTable110_returnsInterpolated() {
-        val table = FontScaleConverterFactory.forScale(1.1F)!!
-
-        assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(1.1f)
-        assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(8f * 1.1f)
-        assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(11f)
-        assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(5f * 1.1f)
-        assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
-        assertThat(table.convertSpToDp(50F)).isLessThan(50f * 1.1f)
-        assertThat(table.convertSpToDp(100F)).isLessThan(100f * 1.1f)
-    }
-
     @Test
     fun missingLookupTable199_returnsInterpolated() {
         val table = FontScaleConverterFactory.forScale(1.9999F)!!
@@ -96,21 +94,51 @@
         assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC)
+    fun missingLookupTable_cachesInterpolated() {
+        val table = FontScaleConverterFactory.forScale(1.6F)!!
+
+        assertThat(FontScaleConverterFactory.sLookupTables.contains((1.6F * 100).toInt())).isTrue()
+        // Double check known existing values
+        assertThat(FontScaleConverterFactory.sLookupTables.contains((1.5F * 100).toInt())).isTrue()
+        assertThat(FontScaleConverterFactory.sLookupTables.contains((1.7F * 100).toInt())).isFalse()
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC)
+    fun missingLookupTablePastEnd_cachesLinear() {
+        val table = FontScaleConverterFactory.forScale(3F)!!
+
+        assertThat(FontScaleConverterFactory.sLookupTables.contains((3F * 100).toInt())).isTrue()
+        // Double check known existing values
+        assertThat(FontScaleConverterFactory.sLookupTables.contains((1.5F * 100).toInt())).isTrue()
+        assertThat(FontScaleConverterFactory.sLookupTables.contains((1.7F * 100).toInt())).isFalse()
+    }
+
     @SmallTest
+    @Test
     fun missingLookupTableNegativeReturnsNull() {
         assertThat(FontScaleConverterFactory.forScale(-1F)).isNull()
     }
 
     @SmallTest
+    @Test
     fun unnecessaryFontScalesReturnsNull() {
         assertThat(FontScaleConverterFactory.forScale(0F)).isNull()
         assertThat(FontScaleConverterFactory.forScale(1F)).isNull()
+        assertThat(FontScaleConverterFactory.forScale(1.1F)).isNull()
         assertThat(FontScaleConverterFactory.forScale(0.85F)).isNull()
     }
 
     @SmallTest
+    @Test
     fun tablesMatchAndAreMonotonicallyIncreasing() {
-        FontScaleConverterFactory.LOOKUP_TABLES.forEach { _, lookupTable ->
+        FontScaleConverterFactory.sLookupTables.forEach { _, lookupTable ->
+            if (lookupTable !is FontScaleConverterImpl) {
+                throw IllegalStateException("Didn't return a FontScaleConverterImpl")
+            }
+
             assertThat(lookupTable.mToDpValues).hasLength(lookupTable.mFromSpValues.size)
             assertThat(lookupTable.mToDpValues).isNotEmpty()
 
@@ -123,6 +151,7 @@
     }
 
     @SmallTest
+    @Test
     fun testIsNonLinearFontScalingActive() {
         assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1f)).isFalse()
         assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(0f)).isFalse()
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt
index bfa8c9a..2c61442 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt
@@ -86,13 +86,13 @@
     }
 
     private fun createTable(vararg pairs: Pair<Float, Float>) =
-        FontScaleConverter(
+        FontScaleConverterImpl(
             pairs.map { it.first }.toFloatArray(),
             pairs.map { it.second }.toFloatArray()
         )
 
     private fun verifyConversionBothWays(
-        table: FontScaleConverter,
+        table: FontScaleConverterImpl,
         expectedDp: Float,
         spToConvert: Float
     ) {
diff --git a/core/tests/coretests/src/android/database/CursorWindowPerformanceTest.java b/core/tests/coretests/src/android/database/CursorWindowPerformanceTest.java
new file mode 100644
index 0000000..c6d3ebf
--- /dev/null
+++ b/core/tests/coretests/src/android/database/CursorWindowPerformanceTest.java
@@ -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 android.database;
+
+import android.test.PerformanceTestCase;
+
+import androidx.test.filters.SmallTest;
+
+import junit.framework.TestCase;
+
+/**
+ * <pre>
+ * m -j44 FrameworksCoreTests
+ * adb install -r -g \
+ *      ${ANDROID_PRODUCT_OUT}/testcases/FrameworksCoreTests/arm64/FrameworksCoreTests.apk
+ * adb shell am instrument -r -e perf true \
+ *      -e class 'android.database.CursorWindowPerformanceTest'
+ *      -w 'com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner'
+ * </pre>
+ */
+public class CursorWindowPerformanceTest extends TestCase implements PerformanceTestCase {
+
+    private final CursorWindowTest mTest = new CursorWindowTest();
+
+    @Override
+    public boolean isPerformanceOnly() {
+        return true;
+    }
+
+    // These test can only be run once.
+    @Override
+    public int startPerformance(Intermediates intermediates) {
+        return 1;
+    }
+
+    @SmallTest
+    public void testConstructor_WithName() {
+        mTest.testConstructor_WithName();
+    }
+
+    @SmallTest
+    public void testConstructorWithEmptyName() {
+        mTest.testConstructorWithEmptyName();
+    }
+
+    @SmallTest
+    public void testValues() {
+        mTest.testValues();
+    }
+}
diff --git a/core/tests/coretests/src/android/database/CursorWindowTest.java b/core/tests/coretests/src/android/database/CursorWindowTest.java
index 123da3e..255020a 100644
--- a/core/tests/coretests/src/android/database/CursorWindowTest.java
+++ b/core/tests/coretests/src/android/database/CursorWindowTest.java
@@ -16,25 +16,29 @@
 
 package android.database;
 
-import android.test.PerformanceTestCase;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import android.database.sqlite.SQLiteException;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
 
-import junit.framework.TestCase;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.Arrays;
 
-public class CursorWindowTest extends TestCase implements PerformanceTestCase {
-    public boolean isPerformanceOnly() {
-        return false;
-    }
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CursorWindowTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
-    // These test can only be run once.
-    public int startPerformance(Intermediates intermediates) {
-        return 1;
-    }
-
-    @SmallTest
+    @Test
     public void testConstructor_WithName() {
         CursorWindow window = new CursorWindow("MyWindow");
         assertEquals("MyWindow", window.getName());
@@ -42,7 +46,7 @@
         window.close();
     }
 
-    @SmallTest
+    @Test
     public void testConstructorWithEmptyName() {
         CursorWindow window = new CursorWindow("");
         assertEquals("<unnamed>", window.getName());
@@ -50,7 +54,7 @@
         window.close();
     }
 
-    @SmallTest
+    @Test
     public void testConstructorWithNullName() {
         CursorWindow window = new CursorWindow(null);
         assertEquals("<unnamed>", window.getName());
@@ -58,7 +62,7 @@
         window.close();
     }
 
-    @SmallTest
+    @Test
     public void testDeprecatedConstructor() {
         @SuppressWarnings("deprecation")
         CursorWindow window = new CursorWindow(true /*this argument is ignored*/);
@@ -67,7 +71,7 @@
         window.close();
     }
 
-    @SmallTest
+    @Test
     public void testValues() {
         CursorWindow window = new CursorWindow("MyWindow");
         doTestValues(window);
@@ -77,17 +81,25 @@
     private void doTestValues(CursorWindow window) {
         assertTrue(window.setNumColumns(7));
         assertTrue(window.allocRow());
+        assertEquals(window.getType(0, 0), Cursor.FIELD_TYPE_NULL);
+
         double db1 = 1.26;
         assertTrue(window.putDouble(db1, 0, 0));
+        assertEquals(window.getType(0, 0), Cursor.FIELD_TYPE_FLOAT);
         double db2 = window.getDouble(0, 0);
-        assertEquals(db1, db2);
+        assertEquals(db1, db2, 0.01);
+        assertEquals(1, window.getInt(0, 0));
+        assertEquals("1.26", window.getString(0, 0));
+        assertThrows(SQLiteException.class, () -> window.getBlob(0, 0));
 
         long int1 = Long.MAX_VALUE;
         assertTrue(window.putLong(int1, 0, 1));
+        assertEquals(window.getType(0, 1), Cursor.FIELD_TYPE_INTEGER);
         long int2 = window.getLong(0, 1);
         assertEquals(int1, int2);
 
         assertTrue(window.putString("1198032740000", 0, 3));
+        assertEquals(window.getType(0, 3), Cursor.FIELD_TYPE_STRING);
         assertEquals("1198032740000", window.getString(0, 3));
         assertEquals(1198032740000L, window.getLong(0, 3));
 
@@ -97,13 +109,14 @@
         
         assertTrue(window.putString(Double.toString(42.0), 0, 4));
         assertEquals(Double.toString(42.0), window.getString(0, 4));
-        assertEquals(42.0, window.getDouble(0, 4));
+        assertEquals(42.0, window.getDouble(0, 4), 0.01);
         
         // put blob
         byte[] blob = new byte[1000];
         byte value = 99;
         Arrays.fill(blob, value);
         assertTrue(window.putBlob(blob, 0, 6));
+        assertEquals(window.getType(0, 6), Cursor.FIELD_TYPE_BLOB);
         assertTrue(Arrays.equals(blob, window.getBlob(0, 6)));
     }
 }
diff --git a/core/tests/coretests/src/android/hardware/face/FaceSensorConfigurationsTest.java b/core/tests/coretests/src/android/hardware/face/FaceSensorConfigurationsTest.java
new file mode 100644
index 0000000..da3a465
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/face/FaceSensorConfigurationsTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.hardware.face;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.face.SensorProps;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.function.Function;
+
+@Presubmit
+@SmallTest
+public class FaceSensorConfigurationsTest {
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+    @Mock
+    private Context mContext;
+    @Mock
+    private Resources mResources;
+    @Mock
+    private IFace mFace;
+    @Mock
+    private Function<String, IFace> mGetIFace;
+
+    private final String[] mAidlInstances = new String[]{"default", "virtual"};
+    private String[] mHidlConfigStrings = new String[]{"0:2:15", "0:8:15"};
+    private FaceSensorConfigurations mFaceSensorConfigurations;
+
+    @Before
+    public void setUp() throws RemoteException {
+        when(mGetIFace.apply(anyString())).thenReturn(mFace);
+        when(mFace.getSensorProps()).thenReturn(new SensorProps[]{});
+        when(mContext.getResources()).thenReturn(mResources);
+    }
+
+    @Test
+    public void testAidlInstanceSensorProps() {
+        mFaceSensorConfigurations = new FaceSensorConfigurations(false);
+        mFaceSensorConfigurations.addAidlConfigs(mAidlInstances, mGetIFace);
+
+        assertThat(mFaceSensorConfigurations.hasSensorConfigurations()).isTrue();
+        assertThat(!mFaceSensorConfigurations.isSingleSensorConfigurationPresent()).isTrue();
+        assertThat(mFaceSensorConfigurations.getResetLockoutRequiresChallenge())
+                .isFalse();
+    }
+
+    @Test
+    public void testHidlConfigStrings() {
+        mFaceSensorConfigurations = new FaceSensorConfigurations(true);
+        mFaceSensorConfigurations.addHidlConfigs(mHidlConfigStrings, mContext);
+
+        assertThat(mFaceSensorConfigurations.isSingleSensorConfigurationPresent()).isTrue();
+        assertThat(mFaceSensorConfigurations.getResetLockoutRequiresChallenge())
+                .isTrue();
+    }
+
+    @Test
+    public void testHidlConfigStrings_incorrectFormat() {
+        mHidlConfigStrings = new String[]{"0:8:15", "0:2", "0:face:15"};
+        mFaceSensorConfigurations = new FaceSensorConfigurations(true);
+        mFaceSensorConfigurations.addHidlConfigs(mHidlConfigStrings, mContext);
+
+        assertThat(mFaceSensorConfigurations.isSingleSensorConfigurationPresent()).isTrue();
+        assertThat(mFaceSensorConfigurations.getResetLockoutRequiresChallenge())
+                .isTrue();
+    }
+}
diff --git a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintSensorConfigurationsTest.java b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintSensorConfigurationsTest.java
new file mode 100644
index 0000000..613089c
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintSensorConfigurationsTest.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.hardware.fingerprint;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.SensorProps;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.function.Function;
+
+
+@Presubmit
+@SmallTest
+public class FingerprintSensorConfigurationsTest {
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+    @Mock
+    private Context mContext;
+    @Mock
+    private Resources mResources;
+    @Mock
+    private IFingerprint mFingerprint;
+    @Mock
+    private Function<String, IFingerprint> mGetIFingerprint;
+
+    private final String[] mAidlInstances = new String[]{"default", "virtual"};
+    private String[] mHidlConfigStrings = new String[]{"0:2:15", "0:8:15"};
+    private FingerprintSensorConfigurations mFingerprintSensorConfigurations;
+
+    @Before
+    public void setUp() throws RemoteException {
+        when(mGetIFingerprint.apply(anyString())).thenReturn(mFingerprint);
+        when(mFingerprint.getSensorProps()).thenReturn(new SensorProps[]{});
+        when(mContext.getResources()).thenReturn(mResources);
+    }
+
+    @Test
+    public void testAidlInstanceSensorProps() {
+        mFingerprintSensorConfigurations = new FingerprintSensorConfigurations(
+                true /* resetLockoutRequiresHardwareAuthToken */);
+        mFingerprintSensorConfigurations.addAidlSensors(mAidlInstances, mGetIFingerprint);
+
+        assertThat(mFingerprintSensorConfigurations.hasSensorConfigurations()).isTrue();
+        assertThat(!mFingerprintSensorConfigurations.isSingleSensorConfigurationPresent())
+                .isTrue();
+        assertThat(mFingerprintSensorConfigurations.getResetLockoutRequiresHardwareAuthToken())
+                .isTrue();
+    }
+
+    @Test
+    public void testHidlConfigStrings() {
+        mFingerprintSensorConfigurations = new FingerprintSensorConfigurations(
+                false /* resetLockoutRequiresHardwareAuthToken */);
+        mFingerprintSensorConfigurations.addHidlSensors(mHidlConfigStrings, mContext);
+
+        assertThat(mFingerprintSensorConfigurations.isSingleSensorConfigurationPresent()).isTrue();
+        assertThat(mFingerprintSensorConfigurations.getResetLockoutRequiresHardwareAuthToken())
+                .isFalse();
+    }
+
+    @Test
+    public void testHidlConfigStrings_incorrectFormat() {
+        mHidlConfigStrings = new String[]{"0:8:15", "0:2", "0:fingerprint:15"};
+        mFingerprintSensorConfigurations = new FingerprintSensorConfigurations(
+                false /* resetLockoutRequiresHardwareAuthToken */);
+        mFingerprintSensorConfigurations.addHidlSensors(mHidlConfigStrings, mContext);
+
+        assertThat(mFingerprintSensorConfigurations.isSingleSensorConfigurationPresent()).isTrue();
+        assertThat(mFingerprintSensorConfigurations.getResetLockoutRequiresHardwareAuthToken())
+                .isFalse();
+    }
+}
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index 68c0693..a709d7b 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -84,6 +84,7 @@
             /* progress = */ 0,
             /* velocityX = */ 0,
             /* velocityY = */ 0,
+            /* triggerBack = */ false,
             /* swipeEdge = */ BackEvent.EDGE_LEFT,
             /* departingAnimationTarget = */ null);
 
diff --git a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
index 6bdc06a..de55b07 100644
--- a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
+++ b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
@@ -63,7 +63,7 @@
         createPhoneWindowWithTheme(R.style.LayoutInDisplayCutoutModeUnset);
         installDecor();
 
-        if (mPhoneWindow.mDefaultEdgeToEdge && !mPhoneWindow.isFloating()) {
+        if (mPhoneWindow.mEdgeToEdgeEnforced && !mPhoneWindow.isFloating()) {
             assertThat(mPhoneWindow.getAttributes().layoutInDisplayCutoutMode,
                     is(LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS));
         } else {
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index aaddf0e..742d5a2 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -379,12 +379,6 @@
       "group": "WM_DEBUG_IME",
       "at": "com\/android\/server\/wm\/DisplayContent.java"
     },
-    "-1770075711": {
-      "message": "Adding window client %s that is dead, aborting.",
-      "level": "WARN",
-      "group": "WM_ERROR",
-      "at": "com\/android\/server\/wm\/WindowManagerService.java"
-    },
     "-1768557332": {
       "message": "removeWallpaperAnimation()",
       "level": "DEBUG",
@@ -3253,6 +3247,12 @@
       "group": "WM_DEBUG_LOCKTASK",
       "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
     },
+    "723575093": {
+      "message": "Attempted to add window with a client %s that is dead. Aborting.",
+      "level": "WARN",
+      "group": "WM_ERROR",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
     "726205185": {
       "message": "Moving to DESTROYED: %s (destroy skipped)",
       "level": "VERBOSE",
@@ -4237,12 +4237,6 @@
       "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
-    "1720696061": {
-      "message": "Adding window to Display that has been removed.",
-      "level": "WARN",
-      "group": "WM_ERROR",
-      "at": "com\/android\/server\/wm\/WindowManagerService.java"
-    },
     "1730300180": {
       "message": "PendingStartTransaction found",
       "level": "VERBOSE",
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 3e7f99b..ed8c4f3 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -344,7 +344,9 @@
         if (activities == null) {
             return null;
         }
-        return new ActivityStack(activities, isEmpty(), mToken);
+        // Already checked nullity in collectNonFinishingActivities.
+        final Rect bounds = getInfo().getConfiguration().windowConfiguration.getBounds();
+        return new ActivityStack(activities, isEmpty(), mToken, bounds, mOverlayTag);
     }
 
     /** Adds the activity that will be reparented to this container. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationConstants.java
index e06d3ef..5b0de50 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationConstants.java
@@ -21,5 +21,4 @@
  */
 class BackAnimationConstants {
     static final float UPDATE_SYSUI_FLAGS_THRESHOLD = 0.20f;
-    static final float PROGRESS_COMMIT_THRESHOLD = 0.1f;
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index d8c691b..a498236 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -70,9 +70,11 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.annotations.ShellBackgroundThread;
 import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 
+import java.io.PrintWriter;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -124,6 +126,7 @@
     private final Context mContext;
     private final ContentResolver mContentResolver;
     private final ShellController mShellController;
+    private final ShellCommandHandler mShellCommandHandler;
     private final ShellExecutor mShellExecutor;
     private final Handler mBgHandler;
 
@@ -180,7 +183,8 @@
             @NonNull @ShellBackgroundThread Handler backgroundHandler,
             Context context,
             @NonNull BackAnimationBackground backAnimationBackground,
-            ShellBackAnimationRegistry shellBackAnimationRegistry) {
+            ShellBackAnimationRegistry shellBackAnimationRegistry,
+            ShellCommandHandler shellCommandHandler) {
         this(
                 shellInit,
                 shellController,
@@ -190,7 +194,8 @@
                 context,
                 context.getContentResolver(),
                 backAnimationBackground,
-                shellBackAnimationRegistry);
+                shellBackAnimationRegistry,
+                shellCommandHandler);
     }
 
     @VisibleForTesting
@@ -203,7 +208,8 @@
             Context context,
             ContentResolver contentResolver,
             @NonNull BackAnimationBackground backAnimationBackground,
-            ShellBackAnimationRegistry shellBackAnimationRegistry) {
+            ShellBackAnimationRegistry shellBackAnimationRegistry,
+            ShellCommandHandler shellCommandHandler) {
         mShellController = shellController;
         mShellExecutor = shellExecutor;
         mActivityTaskManager = activityTaskManager;
@@ -219,6 +225,7 @@
                 .build();
         mShellBackAnimationRegistry = shellBackAnimationRegistry;
         mLatencyTracker = LatencyTracker.getInstance(mContext);
+        mShellCommandHandler = shellCommandHandler;
     }
 
     private void onInit() {
@@ -227,6 +234,7 @@
         createAdapter();
         mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
                 this::createExternalInterface, this);
+        mShellCommandHandler.addDumpCallback(this::dump, this);
     }
 
     private void setupAnimationDeveloperSettingsObserver(
@@ -968,4 +976,20 @@
                 };
         mBackAnimationAdapter = new BackAnimationAdapter(runner);
     }
+
+    /**
+     * Description of current BackAnimationController state.
+     */
+    private void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + "BackAnimationController state:");
+        pw.println(prefix + "  mEnableAnimations=" + mEnableAnimations.get());
+        pw.println(prefix + "  mBackGestureStarted=" + mBackGestureStarted);
+        pw.println(prefix + "  mPostCommitAnimationInProgress=" + mPostCommitAnimationInProgress);
+        pw.println(prefix + "  mShouldStartOnNextMoveEvent=" + mShouldStartOnNextMoveEvent);
+        pw.println(prefix + "  mCurrentTracker state:");
+        mCurrentTracker.dump(pw, prefix + "    ");
+        pw.println(prefix + "  mQueuedTracker state:");
+        mQueuedTracker.dump(pw, prefix + "    ");
+    }
+
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
index 215a6cc..30d5edb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
@@ -18,9 +18,9 @@
 
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static android.window.BackEvent.EDGE_RIGHT;
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY;
-import static com.android.wm.shell.back.BackAnimationConstants.PROGRESS_COMMIT_THRESHOLD;
 import static com.android.wm.shell.back.BackAnimationConstants.UPDATE_SYSUI_FLAGS_THRESHOLD;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
 
@@ -91,7 +91,7 @@
                 }
             };
     private static final float MIN_WINDOW_ALPHA = 0.01f;
-    private static final float WINDOW_X_SHIFT_DP = 96;
+    private static final float WINDOW_X_SHIFT_DP = 48;
     private static final int SCALE_FACTOR = 100;
     // TODO(b/264710590): Use the progress commit threshold from ViewConfiguration once it exists.
     private static final float TARGET_COMMIT_PROGRESS = 0.5f;
@@ -126,6 +126,8 @@
     private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
 
     private boolean mBackInProgress = false;
+    private boolean mIsRightEdge;
+    private boolean mTriggerBack = false;
 
     private PointF mTouchPos = new PointF();
     private IRemoteAnimationFinishedCallback mFinishCallback;
@@ -209,6 +211,7 @@
 
     private void finishAnimation() {
         if (mEnteringTarget != null) {
+            mTransaction.setCornerRadius(mEnteringTarget.leash, 0);
             mEnteringTarget.leash.release();
             mEnteringTarget = null;
         }
@@ -241,14 +244,15 @@
 
     private void onGestureProgress(@NonNull BackEvent backEvent) {
         if (!mBackInProgress) {
+            mIsRightEdge = backEvent.getSwipeEdge() == EDGE_RIGHT;
             mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
             mBackInProgress = true;
         }
         mTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
 
         float progress = backEvent.getProgress();
-        float springProgress = (progress > PROGRESS_COMMIT_THRESHOLD
-                ? mapLinear(progress, 0.1f, 1, TARGET_COMMIT_PROGRESS, 1)
+        float springProgress = (mTriggerBack
+                ? mapLinear(progress, 0f, 1, TARGET_COMMIT_PROGRESS, 1)
                 : mapLinear(progress, 0, 1f, 0, TARGET_COMMIT_PROGRESS)) * SCALE_FACTOR;
         mLeavingProgressSpring.animateToFinalPosition(springProgress);
         mEnteringProgressSpring.animateToFinalPosition(springProgress);
@@ -312,7 +316,7 @@
             transformWithProgress(
                     mEnteringProgress,
                     Math.max(
-                            smoothstep(ENTER_ALPHA_THRESHOLD, 1, mEnteringProgress),
+                            smoothstep(ENTER_ALPHA_THRESHOLD, 0.7f, mEnteringProgress),
                             MIN_WINDOW_ALPHA),  /* alpha */
                     mEnteringTarget.leash,
                     mEnteringRect,
@@ -337,14 +341,13 @@
                     mClosingTarget.leash,
                     mClosingRect,
                     0,
-                    mWindowXShift
+                    mIsRightEdge ? 0 : mWindowXShift
             );
         }
     }
 
     private void transformWithProgress(float progress, float alpha, SurfaceControl surface,
             RectF targetRect, float deltaXMin, float deltaXMax) {
-        final float touchY = mTouchPos.y;
 
         final int width = mStartTaskRect.width();
         final int height = mStartTaskRect.height();
@@ -376,12 +379,14 @@
     private final class Callback extends IOnBackInvokedCallback.Default {
         @Override
         public void onBackStarted(BackMotionEvent backEvent) {
+            mTriggerBack = backEvent.getTriggerBack();
             mProgressAnimator.onBackStarted(backEvent,
                     CrossActivityBackAnimation.this::onGestureProgress);
         }
 
         @Override
         public void onBackProgressed(@NonNull BackMotionEvent backEvent) {
+            mTriggerBack = backEvent.getTriggerBack();
             mProgressAnimator.onBackProgressed(backEvent);
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
index 4bd56d4..6213f62 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
@@ -24,6 +24,8 @@
 import android.window.BackEvent;
 import android.window.BackMotionEvent;
 
+import java.io.PrintWriter;
+
 /**
  * Helper class to record the touch location for gesture and generate back events.
  */
@@ -129,6 +131,7 @@
                 /* progress = */ 0,
                 /* velocityX = */ 0,
                 /* velocityY = */ 0,
+                /* triggerBack = */ mTriggerBack,
                 /* swipeEdge = */ mSwipeEdge,
                 /* departingAnimationTarget = */ target);
     }
@@ -204,6 +207,7 @@
                 /* progress = */ progress,
                 /* velocityX = */ mLatestVelocityX,
                 /* velocityY = */ mLatestVelocityY,
+                /* triggerBack = */ mTriggerBack,
                 /* swipeEdge = */ mSwipeEdge,
                 /* departingAnimationTarget = */ null);
     }
@@ -219,6 +223,12 @@
         mNonLinearFactor = nonLinearFactor;
     }
 
+    void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + "TouchTracker state:");
+        pw.println(prefix + "  mState=" + mState);
+        pw.println(prefix + "  mTriggerBack=" + mTriggerBack);
+    }
+
     enum TouchTrackerState {
         INITIAL, ACTIVE, FINISHED
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEducationController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEducationController.kt
index e57f02c..bd47082 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEducationController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEducationController.kt
@@ -40,7 +40,13 @@
 
     /** Whether education view should show for the collapsed stack. */
     fun shouldShowStackEducation(bubble: BubbleViewProvider?): Boolean {
-        val shouldShow = bubble != null &&
+        if (BubbleDebugConfig.neverShowUserEducation(context)) {
+            logDebug("Show stack edu: never")
+            return false
+        }
+
+        val shouldShow =
+            bubble != null &&
                 bubble.isConversationBubble && // show education for conversation bubbles only
                 (!hasSeenStackEducation || BubbleDebugConfig.forceShowUserEducation(context))
         logDebug("Show stack edu: $shouldShow")
@@ -49,7 +55,13 @@
 
     /** Whether the educational view should show for the expanded view "manage" menu. */
     fun shouldShowManageEducation(bubble: BubbleViewProvider?): Boolean {
-        val shouldShow = bubble != null &&
+        if (BubbleDebugConfig.neverShowUserEducation(context)) {
+            logDebug("Show manage edu: never")
+            return false
+        }
+
+        val shouldShow =
+            bubble != null &&
                 bubble.isConversationBubble && // show education for conversation bubbles only
                 (!hasSeenManageEducation || BubbleDebugConfig.forceShowUserEducation(context))
         logDebug("Show manage edu: $shouldShow")
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index 7f34ee0..f794fef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -23,6 +23,8 @@
 import android.content.Context;
 import android.graphics.Point;
 import android.util.Log;
+import android.util.Size;
+import android.view.View;
 import android.widget.FrameLayout;
 
 import androidx.annotation.Nullable;
@@ -33,6 +35,7 @@
 import com.android.wm.shell.bubbles.BubblePositioner;
 import com.android.wm.shell.bubbles.BubbleViewProvider;
 import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject.MagneticTarget;
 
 /**
  * Helper class to animate a {@link BubbleBarExpandedView} on a bubble.
@@ -44,6 +47,13 @@
     private static final float EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT = 0.1f;
     private static final float EXPANDED_VIEW_ANIMATE_OUT_SCALE_AMOUNT = .75f;
     private static final int EXPANDED_VIEW_ALPHA_ANIMATION_DURATION = 150;
+    private static final int EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION = 100;
+    /**
+     * Additional scale applied to expanded view when it is positioned inside a magnetic target.
+     */
+    private static final float EXPANDED_VIEW_IN_TARGET_SCALE = 0.75f;
+    private static final int EXPANDED_VIEW_ANIMATE_POSITION_DURATION = 300;
+    private static final int EXPANDED_VIEW_DISMISS_DURATION = 250;
 
     /** Spring config for the expanded view scale-in animation. */
     private final PhysicsAnimator.SpringConfig mScaleInSpringConfig =
@@ -181,7 +191,8 @@
             Log.w(TAG, "Trying to animate collapse without a bubble");
             return;
         }
-
+        bbev.setScaleX(1f);
+        bbev.setScaleY(1f);
         mExpandedViewContainerMatrix.setScaleX(1f);
         mExpandedViewContainerMatrix.setScaleY(1f);
 
@@ -209,11 +220,124 @@
     }
 
     /**
+     * Animates dismissal of currently expanded bubble
+     *
+     * @param endRunnable a runnable to run at the end of the animation
+     */
+    public void animateDismiss(Runnable endRunnable) {
+        mIsExpanded = false;
+        final BubbleBarExpandedView bbev = getExpandedView();
+        if (bbev == null) {
+            Log.w(TAG, "Trying to animate dismiss without a bubble");
+            return;
+        }
+
+        int[] location = bbev.getLocationOnScreen();
+        int diffFromBottom = mPositioner.getScreenRect().bottom - location[1];
+
+        bbev.animate()
+                // 2x distance from bottom so the view flies out
+                .translationYBy(diffFromBottom * 2)
+                .setDuration(EXPANDED_VIEW_DISMISS_DURATION)
+                .withEndAction(endRunnable)
+                .start();
+    }
+
+    /**
+     * Animate current expanded bubble back to its rest position
+     */
+    public void animateToRestPosition() {
+        BubbleBarExpandedView bbev = getExpandedView();
+        if (bbev == null) {
+            Log.w(TAG, "Trying to animate expanded view to rest position without a bubble");
+            return;
+        }
+        Point restPoint = getExpandedViewRestPosition(getExpandedViewSize());
+        bbev.animate()
+                .x(restPoint.x)
+                .y(restPoint.y)
+                .scaleX(1f)
+                .scaleY(1f)
+                .setDuration(EXPANDED_VIEW_ANIMATE_POSITION_DURATION)
+                .setInterpolator(Interpolators.EMPHASIZED_DECELERATE)
+                .withStartAction(() -> bbev.setAnimating(true))
+                .withEndAction(() -> bbev.setAnimating(false))
+                .start();
+    }
+
+    /**
+     * Animates currently expanded bubble into the given {@link MagneticTarget}.
+     *
+     * @param target magnetic target to snap to
+     * @param endRunnable a runnable to run at the end of the animation
+     */
+    public void animateIntoTarget(MagneticTarget target, @Nullable Runnable endRunnable) {
+        BubbleBarExpandedView bbev = getExpandedView();
+        if (bbev == null) {
+            Log.w(TAG, "Trying to snap the expanded view to target without a bubble");
+            return;
+        }
+        Point expandedViewCenter = getViewCenterOnScreen(bbev);
+
+        // Calculate the difference between the target's center coordinates and the object's.
+        // Animating the object's x/y properties by these values will center the object on top
+        // of the magnetic target.
+        float xDiff = target.getCenterOnScreen().x - expandedViewCenter.x;
+        float yDiff = target.getCenterOnScreen().y - expandedViewCenter.y;
+
+        // Calculate scale of expanded view so it fits inside the magnetic target
+        float bbevMaxSide = Math.max(bbev.getWidth(), bbev.getHeight());
+        float targetMaxSide = Math.max(target.getTargetView().getWidth(),
+                target.getTargetView().getHeight());
+        float scale = (targetMaxSide * EXPANDED_VIEW_IN_TARGET_SCALE) / bbevMaxSide;
+
+        bbev.animate()
+                .translationX(bbev.getTranslationX() + xDiff)
+                .translationY(bbev.getTranslationY() + yDiff)
+                .scaleX(scale)
+                .scaleY(scale)
+                .setDuration(EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION)
+                .setInterpolator(Interpolators.EMPHASIZED)
+                .withStartAction(() -> bbev.setAnimating(true))
+                .withEndAction(() -> {
+                    bbev.setAnimating(false);
+                    if (endRunnable != null) {
+                        endRunnable.run();
+                    }
+                })
+                .start();
+    }
+
+    /**
+     * Animate currently expanded view when it is released from dismiss view
+     */
+    public void animateUnstuckFromDismissView() {
+        BubbleBarExpandedView expandedView = getExpandedView();
+        if (expandedView == null) {
+            Log.w(TAG, "Trying to unsnap the expanded view from dismiss without a bubble");
+            return;
+        }
+        expandedView
+                .animate()
+                .scaleX(1f)
+                .scaleY(1f)
+                .setDuration(EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION)
+                .setInterpolator(Interpolators.EMPHASIZED)
+                .withStartAction(() -> expandedView.setAnimating(true))
+                .withEndAction(() -> expandedView.setAnimating(false))
+                .start();
+    }
+
+    /**
      * Cancel current animations
      */
     public void cancelAnimations() {
         PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
         mExpandedViewAlphaAnimator.cancel();
+        BubbleBarExpandedView bbev = getExpandedView();
+        if (bbev != null) {
+            bbev.animate().cancel();
+        }
     }
 
     private @Nullable BubbleBarExpandedView getExpandedView() {
@@ -231,21 +355,42 @@
             return;
         }
 
-        boolean isOverflowExpanded = mExpandedBubble.getKey().equals(BubbleOverflow.KEY);
-        final int padding = mPositioner.getBubbleBarExpandedViewPadding();
-        final int width = mPositioner.getExpandedViewWidthForBubbleBar(isOverflowExpanded);
-        final int height = mPositioner.getExpandedViewHeightForBubbleBar(isOverflowExpanded);
+        final Size size = getExpandedViewSize();
+        Point position = getExpandedViewRestPosition(size);
         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) bbev.getLayoutParams();
-        lp.width = width;
-        lp.height = height;
+        lp.width = size.getWidth();
+        lp.height = size.getHeight();
         bbev.setLayoutParams(lp);
-        if (mLayerView.isOnLeft()) {
-            bbev.setX(mPositioner.getInsets().left + padding);
-        } else {
-            bbev.setX(mPositioner.getAvailableRect().width() - width - padding);
-        }
-        bbev.setY(mPositioner.getExpandedViewBottomForBubbleBar() - height);
+        bbev.setX(position.x);
+        bbev.setY(position.y);
         bbev.updateLocation();
         bbev.maybeShowOverflow();
     }
+
+    private Point getExpandedViewRestPosition(Size size) {
+        final int padding = mPositioner.getBubbleBarExpandedViewPadding();
+        Point point = new Point();
+        if (mLayerView.isOnLeft()) {
+            point.x = mPositioner.getInsets().left + padding;
+        } else {
+            point.x = mPositioner.getAvailableRect().width() - size.getWidth() - padding;
+        }
+        point.y = mPositioner.getExpandedViewBottomForBubbleBar() - size.getHeight();
+        return point;
+    }
+
+    private Size getExpandedViewSize() {
+        boolean isOverflowExpanded = mExpandedBubble.getKey().equals(BubbleOverflow.KEY);
+        final int width = mPositioner.getExpandedViewWidthForBubbleBar(isOverflowExpanded);
+        final int height = mPositioner.getExpandedViewHeightForBubbleBar(isOverflowExpanded);
+        return new Size(width, height);
+    }
+
+    private Point getViewCenterOnScreen(View view) {
+        Point center = new Point();
+        int[] onScreenLocation = view.getLocationOnScreen();
+        center.x = (int) (onScreenLocation[0] + (view.getWidth() / 2f));
+        center.y = (int) (onScreenLocation[1] + (view.getHeight() / 2f));
+        return center;
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
index 4ea18f7..d215450 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
@@ -16,70 +16,67 @@
 
 package com.android.wm.shell.bubbles.bar
 
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
-import android.graphics.PointF
-import android.graphics.Rect
+import android.annotation.SuppressLint
 import android.view.MotionEvent
 import android.view.View
-import com.android.wm.shell.animation.Interpolators
 import com.android.wm.shell.common.bubbles.DismissView
 import com.android.wm.shell.common.bubbles.RelativeTouchListener
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject
 
 /** Controller for handling drag interactions with [BubbleBarExpandedView] */
+@SuppressLint("ClickableViewAccessibility")
 class BubbleBarExpandedViewDragController(
     private val expandedView: BubbleBarExpandedView,
     private val dismissView: DismissView,
+    private val animationHelper: BubbleBarAnimationHelper,
     private val onDismissed: () -> Unit
 ) {
 
+    var isStuckToDismiss: Boolean = false
+        private set
+
+    private var expandedViewInitialTranslationX = 0f
+    private var expandedViewInitialTranslationY = 0f
+    private val magnetizedExpandedView: MagnetizedObject<BubbleBarExpandedView> =
+        MagnetizedObject.magnetizeView(expandedView)
+    private val magnetizedDismissTarget: MagnetizedObject.MagneticTarget
+
     init {
-        expandedView.handleView.setOnTouchListener(HandleDragListener())
-    }
-
-    private fun finishDrag(x: Float, y: Float, viewInitialX: Float, viewInitialY: Float) {
-        val dismissCircleBounds = Rect().apply { dismissView.circle.getBoundsOnScreen(this) }
-        if (dismissCircleBounds.contains(x.toInt(), y.toInt())) {
-            onDismissed()
-        } else {
-            resetExpandedViewPosition(viewInitialX, viewInitialY)
-        }
-        dismissView.hide()
-    }
-
-    private fun resetExpandedViewPosition(initialX: Float, initialY: Float) {
-        val listener =
-            object : AnimatorListenerAdapter() {
-                override fun onAnimationStart(animation: Animator) {
-                    expandedView.isAnimating = true
-                }
-
-                override fun onAnimationEnd(animation: Animator) {
-                    expandedView.isAnimating = false
-                }
+        magnetizedExpandedView.magnetListener = MagnetListener()
+        magnetizedExpandedView.animateStuckToTarget =
+            {
+                    target: MagnetizedObject.MagneticTarget,
+                    _: Float,
+                    _: Float,
+                    _: Boolean,
+                    after: (() -> Unit)? ->
+                animationHelper.animateIntoTarget(target, after)
             }
-        expandedView
-            .animate()
-            .translationX(initialX)
-            .translationY(initialY)
-            .setDuration(RESET_POSITION_ANIM_DURATION)
-            .setInterpolator(Interpolators.EMPHASIZED_DECELERATE)
-            .setListener(listener)
-            .start()
+
+        magnetizedDismissTarget =
+            MagnetizedObject.MagneticTarget(dismissView.circle, dismissView.circle.width)
+        magnetizedExpandedView.addTarget(magnetizedDismissTarget)
+
+        val dragMotionEventHandler = HandleDragListener()
+
+        expandedView.handleView.setOnTouchListener { view, event ->
+            if (event.actionMasked == MotionEvent.ACTION_DOWN) {
+                expandedViewInitialTranslationX = expandedView.translationX
+                expandedViewInitialTranslationY = expandedView.translationY
+            }
+            val magnetConsumed = magnetizedExpandedView.maybeConsumeMotionEvent(event)
+            // Move events can be consumed by the magnetized object
+            if (event.actionMasked == MotionEvent.ACTION_MOVE && magnetConsumed) {
+                return@setOnTouchListener true
+            }
+            return@setOnTouchListener dragMotionEventHandler.onTouch(view, event) || magnetConsumed
+        }
     }
 
     private inner class HandleDragListener : RelativeTouchListener() {
-
-        private val expandedViewRestPosition = PointF()
-
         override fun onDown(v: View, ev: MotionEvent): Boolean {
             // While animating, don't allow new touch events
-            if (expandedView.isAnimating) {
-                return false
-            }
-            expandedViewRestPosition.x = expandedView.translationX
-            expandedViewRestPosition.y = expandedView.translationY
-            return true
+            return !expandedView.isAnimating
         }
 
         override fun onMove(
@@ -90,8 +87,8 @@
             dx: Float,
             dy: Float
         ) {
-            expandedView.translationX = expandedViewRestPosition.x + dx
-            expandedView.translationY = expandedViewRestPosition.y + dy
+            expandedView.translationX = expandedViewInitialTranslationX + dx
+            expandedView.translationY = expandedViewInitialTranslationY + dy
             dismissView.show()
         }
 
@@ -105,16 +102,40 @@
             velX: Float,
             velY: Float
         ) {
-            finishDrag(ev.rawX, ev.rawY, expandedViewRestPosition.x, expandedViewRestPosition.y)
+            finishDrag()
         }
 
         override fun onCancel(v: View, ev: MotionEvent, viewInitialX: Float, viewInitialY: Float) {
-            resetExpandedViewPosition(expandedViewRestPosition.x, expandedViewRestPosition.y)
-            dismissView.hide()
+            finishDrag()
+        }
+
+        private fun finishDrag() {
+            if (!isStuckToDismiss) {
+                animationHelper.animateToRestPosition()
+                dismissView.hide()
+            }
         }
     }
 
-    companion object {
-        const val RESET_POSITION_ANIM_DURATION = 300L
+    private inner class MagnetListener : MagnetizedObject.MagnetListener {
+        override fun onStuckToTarget(target: MagnetizedObject.MagneticTarget) {
+            isStuckToDismiss = true
+        }
+
+        override fun onUnstuckFromTarget(
+            target: MagnetizedObject.MagneticTarget,
+            velX: Float,
+            velY: Float,
+            wasFlungOut: Boolean
+        ) {
+            isStuckToDismiss = false
+            animationHelper.animateUnstuckFromDismissView()
+        }
+
+        override fun onReleasedInTarget(target: MagnetizedObject.MagneticTarget) {
+            onDismissed()
+            dismissView.hide()
+        }
     }
 }
+
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index bdb0e20..1211451 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -18,6 +18,7 @@
 
 import static com.android.wm.shell.animation.Interpolators.ALPHA_IN;
 import static com.android.wm.shell.animation.Interpolators.ALPHA_OUT;
+import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_GESTURE;
 
 import android.annotation.Nullable;
 import android.content.Context;
@@ -36,7 +37,6 @@
 import com.android.wm.shell.bubbles.BubbleOverflow;
 import com.android.wm.shell.bubbles.BubblePositioner;
 import com.android.wm.shell.bubbles.BubbleViewProvider;
-import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.bubbles.DeviceConfig;
 import com.android.wm.shell.bubbles.DismissViewUtils;
 import com.android.wm.shell.common.bubbles.DismissView;
@@ -206,10 +206,13 @@
                 }
             });
 
-            mDragController = new BubbleBarExpandedViewDragController(mExpandedView, mDismissView,
+            mDragController = new BubbleBarExpandedViewDragController(
+                    mExpandedView,
+                    mDismissView,
+                    mAnimationHelper,
                     () -> {
                         mBubbleController.dismissBubble(mExpandedBubble.getKey(),
-                                Bubbles.DISMISS_USER_GESTURE);
+                                DISMISS_USER_GESTURE);
                         return Unit.INSTANCE;
                     });
 
@@ -241,7 +244,11 @@
         mIsExpanded = false;
         final BubbleBarExpandedView viewToRemove = mExpandedView;
         mEducationViewController.hideEducation(/* animated = */ true);
-        mAnimationHelper.animateCollapse(() -> removeView(viewToRemove));
+        if (mDragController != null && mDragController.isStuckToDismiss()) {
+            mAnimationHelper.animateDismiss(() -> removeView(viewToRemove));
+        } else {
+            mAnimationHelper.animateCollapse(() -> removeView(viewToRemove));
+        }
         mBubbleController.getSysuiProxy().onStackExpandChanged(false);
         mExpandedView = null;
         mDragController = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 3c6bc17..fc97c798 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -363,7 +363,8 @@
             @ShellMainThread ShellExecutor shellExecutor,
             @ShellBackgroundThread Handler backgroundHandler,
             BackAnimationBackground backAnimationBackground,
-            Optional<ShellBackAnimationRegistry> shellBackAnimationRegistry) {
+            Optional<ShellBackAnimationRegistry> shellBackAnimationRegistry,
+            ShellCommandHandler shellCommandHandler) {
         if (BackAnimationController.IS_ENABLED) {
             return shellBackAnimationRegistry.map(
                     (animations) ->
@@ -374,7 +375,8 @@
                                     backgroundHandler,
                                     context,
                                     backAnimationBackground,
-                                    animations));
+                                    animations,
+                                    shellCommandHandler));
         }
         return Optional.empty();
     }
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 0448d94..0b8f60e 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
@@ -96,7 +96,7 @@
 
     @Nullable
     private WindowContainerTransaction getExitPipViaExpandTransaction() {
-        if (mPipTaskToken == null || mPinnedTaskLeash == null) {
+        if (mPipTaskToken == null) {
             return null;
         }
         WindowContainerTransaction wct = new WindowContainerTransaction();
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 6200ea5..48a0a46 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
@@ -18,6 +18,7 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_PIP;
 
 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
 
@@ -56,6 +57,8 @@
     private IBinder mAutoEnterButtonNavTransition;
     @Nullable
     private IBinder mExitViaExpandTransition;
+    @Nullable
+    private IBinder mLegacyEnterTransition;
 
     public PipTransition(
             @NonNull ShellInit shellInit,
@@ -98,6 +101,9 @@
         if (isAutoEnterInButtonNavigation(request)) {
             mAutoEnterButtonNavTransition = transition;
             return getEnterPipTransaction(transition, request);
+        } else if (isLegacyEnter(request)) {
+            mLegacyEnterTransition = transition;
+            return getEnterPipTransaction(transition, request);
         }
         return null;
     }
@@ -108,6 +114,9 @@
         if (isAutoEnterInButtonNavigation(request)) {
             outWct.merge(getEnterPipTransaction(transition, request), true /* transfer */);
             mAutoEnterButtonNavTransition = transition;
+        } else if (isLegacyEnter(request)) {
+            outWct.merge(getEnterPipTransaction(transition, request), true /* transfer */);
+            mLegacyEnterTransition = transition;
         }
     }
 
@@ -153,6 +162,10 @@
                 && pipTask.pictureInPictureParams.isAutoEnterEnabled();
     }
 
+    private boolean isLegacyEnter(@NonNull TransitionRequestInfo requestInfo) {
+        return requestInfo.getType() == TRANSIT_PIP;
+    }
+
     @Override
     public boolean startAnimation(@NonNull IBinder transition,
             @NonNull TransitionInfo info,
@@ -161,29 +174,65 @@
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         if (transition == mAutoEnterButtonNavTransition) {
             mAutoEnterButtonNavTransition = null;
-            TransitionInfo.Change pipChange = getPipChange(info);
-            if (pipChange == null) {
-                return false;
-            }
-            mPipTaskToken = pipChange.getContainer();
-
-            // cache the PiP task token and leash
-            mPipScheduler.setPipTaskToken(mPipTaskToken);
-            mPipScheduler.setPinnedTaskLeash(pipChange.getLeash());
-
-            startTransaction.apply();
-            finishCallback.onTransitionFinished(null);
-            return true;
+            return startAutoEnterButtonNavAnimation(info, startTransaction, finishTransaction,
+                    finishCallback);
+        } else if (transition == mLegacyEnterTransition) {
+            mLegacyEnterTransition = null;
+            return startLegacyEnterAnimation(info, startTransaction, finishTransaction,
+                    finishCallback);
         } else if (transition == mExitViaExpandTransition) {
             mExitViaExpandTransition = null;
-            startTransaction.apply();
-            finishCallback.onTransitionFinished(null);
-            onExitPip();
-            return true;
+            return startExpandAnimation(info, startTransaction, finishTransaction, finishCallback);
         }
         return false;
     }
 
+    private boolean startAutoEnterButtonNavAnimation(@NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        TransitionInfo.Change pipChange = getPipChange(info);
+        if (pipChange == null) {
+            return false;
+        }
+        mPipTaskToken = pipChange.getContainer();
+
+        // cache the PiP task token and leash
+        mPipScheduler.setPipTaskToken(mPipTaskToken);
+
+        startTransaction.apply();
+        finishCallback.onTransitionFinished(null);
+        return true;
+    }
+
+    private boolean startLegacyEnterAnimation(@NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        TransitionInfo.Change pipChange = getPipChange(info);
+        if (pipChange == null) {
+            return false;
+        }
+        mPipTaskToken = pipChange.getContainer();
+
+        // cache the PiP task token and leash
+        mPipScheduler.setPipTaskToken(mPipTaskToken);
+
+        startTransaction.apply();
+        finishCallback.onTransitionFinished(null);
+        return true;
+    }
+
+    private boolean startExpandAnimation(@NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        startTransaction.apply();
+        finishCallback.onTransitionFinished(null);
+        onExitPip();
+        return true;
+    }
+
     @Nullable
     private TransitionInfo.Change getPipChange(TransitionInfo info) {
         for (TransitionInfo.Change change : info.getChanges()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index ae21c4b..f58aeac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -277,11 +277,6 @@
         params.token = appToken;
         params.packageName = activityInfo.packageName;
         params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
-
-        if (!context.getResources().getCompatibilityInfo().supportsScreen()) {
-            params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
-        }
-
         params.setTitle("Splash Screen " + title);
         return params;
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 771876f..9ded6ea 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -63,6 +63,7 @@
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.sysui.ShellSharedConstants;
@@ -110,6 +111,8 @@
 
     @Mock
     private InputManager mInputManager;
+    @Mock
+    private ShellCommandHandler mShellCommandHandler;
 
     private BackAnimationController mController;
     private TestableContentResolver mContentResolver;
@@ -145,7 +148,8 @@
                         mContext,
                         mContentResolver,
                         mAnimationBackground,
-                        mShellBackAnimationRegistry);
+                        mShellBackAnimationRegistry,
+                        mShellCommandHandler);
         mShellInit.init();
         mShellExecutor.flushAll();
     }
@@ -298,7 +302,8 @@
                         mContext,
                         mContentResolver,
                         mAnimationBackground,
-                        mShellBackAnimationRegistry);
+                        mShellBackAnimationRegistry,
+                        mShellCommandHandler);
         shellInit.init();
         registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
index 874ef80..91503b1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
@@ -53,6 +53,7 @@
                 /* progress = */ progress,
                 /* velocityX = */ 0,
                 /* velocityY = */ 0,
+                /* triggerBack = */ false,
                 /* swipeEdge = */ BackEvent.EDGE_LEFT,
                 /* departingAnimationTarget = */ null);
     }
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index b9dc618..2501869 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -48,6 +48,7 @@
         "SettingsLibTwoTargetPreference",
         "SettingsLibUsageProgressBarPreference",
         "SettingsLibUtils",
+        "settingslib_media_flags_lib",
         "settingslib_flags_lib",
     ],
 
@@ -78,6 +79,19 @@
 }
 
 java_aconfig_library {
-    name: "settingslib_flags_lib",
+    name: "settingslib_media_flags_lib",
     aconfig_declarations: "settingslib_media_flags",
 }
+
+aconfig_declarations {
+    name: "settingslib_flags",
+    package: "com.android.settingslib.flags",
+    srcs: [
+        "aconfig/settingslib.aconfig",
+    ],
+}
+
+java_aconfig_library {
+    name: "settingslib_flags_lib",
+    aconfig_declarations: "settingslib_flags",
+}
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
new file mode 100644
index 0000000..fd2f9bd
--- /dev/null
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.settingslib.flags"
+
+flag {
+    name: "new_status_bar_icons"
+    namespace: "systemui"
+    description: "Enable new status bar system icons"
+    bug: "314812750"
+}
+
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingConstants.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingConstants.java
index d8475b3..ca0cad7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingConstants.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingConstants.java
@@ -39,23 +39,24 @@
     public static final int[] MEDIA_ROUTING_ATTRIBUTES = new int[] {
             // Stands for STRATEGY_MEDIA, including USAGE_GAME, USAGE_ASSISTANT,
             // USAGE_ASSISTANCE_NAVIGATION_GUIDANCE, USAGE_ASSISTANCE_SONIFICATION
-            AudioAttributes.USAGE_MEDIA
-    };
-
-    public static final int[] RINGTONE_ROUTING_ATTRIBUTE = new int[] {
-            // Stands for STRATEGY_SONIFICATION, including USAGE_ALARM
-            AudioAttributes.USAGE_NOTIFICATION_RINGTONE
-    };
-
-    public static final int[] SYSTEM_SOUNDS_ROUTING_ATTRIBUTES = new int[] {
-            // Stands for STRATEGY_SONIFICATION_RESPECTFUL, including USAGE_NOTIFICATION_EVENT
-            AudioAttributes.USAGE_NOTIFICATION,
+            AudioAttributes.USAGE_MEDIA,
             // Stands for STRATEGY_ACCESSIBILITY
             AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY,
             // Stands for STRATEGY_DTMF
             AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING,
     };
 
+    public static final int[] RINGTONE_ROUTING_ATTRIBUTES = new int[] {
+            // Stands for STRATEGY_SONIFICATION, including USAGE_ALARM
+            AudioAttributes.USAGE_NOTIFICATION_RINGTONE
+    };
+
+    public static final int[] NOTIFICATION_ROUTING_ATTRIBUTES = new int[] {
+            // Stands for STRATEGY_SONIFICATION_RESPECTFUL, including USAGE_NOTIFICATION_EVENT
+            AudioAttributes.USAGE_NOTIFICATION,
+
+    };
+
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({
             RoutingValue.AUTO,
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
index 111a2c1..3a15b71 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
@@ -26,6 +26,7 @@
 import android.media.audiopolicy.AudioProductStrategy;
 import android.os.ParcelUuid;
 import android.provider.Settings;
+import android.util.FeatureFlagUtils;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -42,11 +43,13 @@
     private static final boolean DEBUG = BluetoothUtils.D;
 
     private final ContentResolver mContentResolver;
+    private final Context mContext;
     private final LocalBluetoothManager mBtManager;
     private final List<CachedBluetoothDevice> mCachedDevices;
     private final HearingAidAudioRoutingHelper mRoutingHelper;
     HearingAidDeviceManager(Context context, LocalBluetoothManager localBtManager,
             List<CachedBluetoothDevice> CachedDevices) {
+        mContext = context;
         mContentResolver = context.getContentResolver();
         mBtManager = localBtManager;
         mCachedDevices = CachedDevices;
@@ -56,6 +59,7 @@
     @VisibleForTesting
     HearingAidDeviceManager(Context context, LocalBluetoothManager localBtManager,
             List<CachedBluetoothDevice> cachedDevices, HearingAidAudioRoutingHelper routingHelper) {
+        mContext = context;
         mContentResolver = context.getContentResolver();
         mBtManager = localBtManager;
         mCachedDevices = cachedDevices;
@@ -285,11 +289,13 @@
     }
 
     void onActiveDeviceChanged(CachedBluetoothDevice device) {
-        if (device.isActiveDevice(BluetoothProfile.HEARING_AID) || device.isActiveDevice(
-                BluetoothProfile.LE_AUDIO)) {
-            setAudioRoutingConfig(device);
-        } else {
-            clearAudioRoutingConfig();
+        if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_AUDIO_ROUTING)) {
+            if (device.isActiveDevice(BluetoothProfile.HEARING_AID) || device.isActiveDevice(
+                    BluetoothProfile.LE_AUDIO)) {
+                setAudioRoutingConfig(device);
+            } else {
+                clearAudioRoutingConfig();
+            }
         }
     }
 
@@ -312,7 +318,7 @@
                 Settings.Secure.HEARING_AID_RINGTONE_ROUTING,
                 HearingAidAudioRoutingConstants.RoutingValue.AUTO);
         final int systemSoundsRoutingValue = Settings.Secure.getInt(mContentResolver,
-                Settings.Secure.HEARING_AID_SYSTEM_SOUNDS_ROUTING,
+                Settings.Secure.HEARING_AID_NOTIFICATION_ROUTING,
                 HearingAidAudioRoutingConstants.RoutingValue.AUTO);
 
         setPreferredDeviceRoutingStrategies(
@@ -322,10 +328,10 @@
                 HearingAidAudioRoutingConstants.MEDIA_ROUTING_ATTRIBUTES,
                 hearingDeviceAttributes, mediaRoutingValue);
         setPreferredDeviceRoutingStrategies(
-                HearingAidAudioRoutingConstants.RINGTONE_ROUTING_ATTRIBUTE,
+                HearingAidAudioRoutingConstants.RINGTONE_ROUTING_ATTRIBUTES,
                 hearingDeviceAttributes, ringtoneRoutingValue);
         setPreferredDeviceRoutingStrategies(
-                HearingAidAudioRoutingConstants.SYSTEM_SOUNDS_ROUTING_ATTRIBUTES,
+                HearingAidAudioRoutingConstants.NOTIFICATION_ROUTING_ATTRIBUTES,
                 hearingDeviceAttributes, systemSoundsRoutingValue);
     }
 
@@ -338,10 +344,10 @@
                 HearingAidAudioRoutingConstants.MEDIA_ROUTING_ATTRIBUTES,
                 /* hearingDevice = */ null, HearingAidAudioRoutingConstants.RoutingValue.AUTO);
         setPreferredDeviceRoutingStrategies(
-                HearingAidAudioRoutingConstants.RINGTONE_ROUTING_ATTRIBUTE,
+                HearingAidAudioRoutingConstants.RINGTONE_ROUTING_ATTRIBUTES,
                 /* hearingDevice = */ null, HearingAidAudioRoutingConstants.RoutingValue.AUTO);
         setPreferredDeviceRoutingStrategies(
-                HearingAidAudioRoutingConstants.SYSTEM_SOUNDS_ROUTING_ATTRIBUTES,
+                HearingAidAudioRoutingConstants.NOTIFICATION_ROUTING_ATTRIBUTES,
                 /* hearingDevice = */ null, HearingAidAudioRoutingConstants.RoutingValue.AUTO);
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
index 9084aa2..e83b9bc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
@@ -22,6 +22,7 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.drawable.Drawable;
+import android.multiuser.Flags;
 import android.net.Uri;
 import android.util.Log;
 import android.widget.ImageView;
@@ -59,6 +60,9 @@
     private static final String IMAGES_DIR = "multi_user";
     private static final String NEW_USER_PHOTO_FILE_NAME = "NewUserPhoto.png";
 
+    private static final String AVATAR_PICKER_ACTION = "com.android.avatarpicker"
+            + ".FULL_SCREEN_ACTIVITY";
+
     private final Activity mActivity;
     private final ActivityStarter mActivityStarter;
     private final ImageView mImageView;
@@ -105,7 +109,6 @@
                 onPhotoCropped(data.getData());
                 return true;
             }
-
         }
         return false;
     }
@@ -115,7 +118,13 @@
     }
 
     private void showAvatarPicker() {
-        Intent intent = new Intent(mImageView.getContext(), AvatarPickerActivity.class);
+        Intent intent;
+        if (Flags.avatarSync()) {
+            intent = new Intent(AVATAR_PICKER_ACTION);
+            intent.addCategory(Intent.CATEGORY_DEFAULT);
+        } else {
+            intent = new Intent(mImageView.getContext(), AvatarPickerActivity.class);
+        }
         intent.putExtra(AvatarPickerActivity.EXTRA_FILE_AUTHORITY, mFileAuthority);
         mActivityStarter.startActivityForResult(intent, REQUEST_CODE_PICK_AVATAR);
     }
diff --git a/packages/SettingsLib/tests/integ/Android.bp b/packages/SettingsLib/tests/integ/Android.bp
index 4b4caf5..644b72c 100644
--- a/packages/SettingsLib/tests/integ/Android.bp
+++ b/packages/SettingsLib/tests/integ/Android.bp
@@ -56,7 +56,7 @@
         "SettingsLibDeviceStateRotationLock",
         "SettingsLibSettingsSpinner",
         "SettingsLibUsageProgressBarPreference",
-        "settingslib_flags_lib",
+        "settingslib_media_flags_lib",
     ],
 
     dxflags: ["--multi-dex"],
diff --git a/packages/SettingsLib/tests/robotests/Android.bp b/packages/SettingsLib/tests/robotests/Android.bp
index 732c336..f4ddd0a 100644
--- a/packages/SettingsLib/tests/robotests/Android.bp
+++ b/packages/SettingsLib/tests/robotests/Android.bp
@@ -50,7 +50,7 @@
         "androidx.test.core",
         "androidx.core_core",
         "flag-junit",
-        "settingslib_flags_lib",
+        "settingslib_media_flags_lib",
         "testng", // TODO: remove once JUnit on Android provides assertThrows
     ],
     java_resource_dirs: ["config"],
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 ea10944..24fd06e 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
@@ -42,6 +42,7 @@
 import android.media.AudioManager;
 import android.media.audiopolicy.AudioProductStrategy;
 import android.os.Parcel;
+import android.util.FeatureFlagUtils;
 
 import androidx.test.core.app.ApplicationProvider;
 
@@ -109,6 +110,7 @@
 
     @Before
     public void setUp() {
+        FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SETTINGS_AUDIO_ROUTING, true);
         when(mDevice1.getAddress()).thenReturn(DEVICE_ADDRESS_1);
         when(mDevice2.getAddress()).thenReturn(DEVICE_ADDRESS_2);
         when(mDevice1.getName()).thenReturn(DEVICE_NAME_1);
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 2167a8a..5004f25 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -247,7 +247,7 @@
         Settings.Secure.HEARING_AID_RINGTONE_ROUTING,
         Settings.Secure.HEARING_AID_CALL_ROUTING,
         Settings.Secure.HEARING_AID_MEDIA_ROUTING,
-        Settings.Secure.HEARING_AID_SYSTEM_SOUNDS_ROUTING,
+        Settings.Secure.HEARING_AID_NOTIFICATION_ROUTING,
         Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
         Settings.Secure.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED,
         Settings.Secure.SEARCH_LONG_PRESS_HOME_ENABLED,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 868623f..0b0e182 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -396,7 +396,7 @@
                 new DiscreteValueValidator(new String[] {"0", "1", "2"}));
         VALIDATORS.put(Secure.HEARING_AID_MEDIA_ROUTING,
                 new DiscreteValueValidator(new String[] {"0", "1", "2"}));
-        VALIDATORS.put(Secure.HEARING_AID_SYSTEM_SOUNDS_ROUTING,
+        VALIDATORS.put(Secure.HEARING_AID_NOTIFICATION_ROUTING,
                 new DiscreteValueValidator(new String[] {"0", "1", "2"}));
         VALIDATORS.put(Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_EDGE_HAPTIC_ENABLED,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 5afcd5d..1670a70 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1866,8 +1866,8 @@
                 Settings.Secure.HEARING_AID_MEDIA_ROUTING,
                 SecureSettingsProto.Accessibility.HEARING_AID_MEDIA_ROUTING);
         dumpSetting(s, p,
-                Settings.Secure.HEARING_AID_SYSTEM_SOUNDS_ROUTING,
-                SecureSettingsProto.Accessibility.HEARING_AID_SYSTEM_SOUNDS_ROUTING);
+                Settings.Secure.HEARING_AID_NOTIFICATION_ROUTING,
+                SecureSettingsProto.Accessibility.HEARING_AID_NOTIFICATION_ROUTING);
         dumpSetting(s, p,
                 Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
                 SecureSettingsProto.Accessibility.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED);
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index f10ac1b..cc5dfc6 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -277,6 +277,7 @@
         "SystemUIPluginLib",
         "SystemUISharedLib",
         "SystemUICustomizationLib",
+        "SystemUICustomizationTestUtils",
         "SystemUI-statsd",
         "SettingsLib",
         "com_android_systemui_flags_lib",
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index facb244..ab4fe76 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -160,6 +160,15 @@
 }
 
 flag {
+    name: "edge_back_gesture_handler_thread"
+    namespace: "systemui"
+    description: "Moves the EdgeBackGestureHandler window, which is used for rendering the back "
+        "arrow, to a separate thread. Previously, the EdgeBackGestureHandler window would share "
+        "the main thread with the rest of System UI."
+    bug: "304583132"
+}
+
+flag {
     name: "new_aod_transition"
     namespace: "systemui"
     description: "New LOCKSCREEN <=> AOD transition"
@@ -230,3 +239,12 @@
    description: "Enable special visual and haptic effects for quick settings tiles with long-press actions"
    bug: "229856884"
 }
+
+flag {
+    name: "status_bar_static_inout_indicators"
+    namespace: "systemui"
+    description: "(Upstream request) Always show the network activity inout indicators and "
+        "prefer using alpha to distinguish network activity."
+    bug: "310715220"
+}
+
diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/core/Android.bp
similarity index 100%
rename from packages/SystemUI/animation/Android.bp
rename to packages/SystemUI/animation/core/Android.bp
diff --git a/packages/SystemUI/animation/AndroidManifest.xml b/packages/SystemUI/animation/core/AndroidManifest.xml
similarity index 100%
rename from packages/SystemUI/animation/AndroidManifest.xml
rename to packages/SystemUI/animation/core/AndroidManifest.xml
diff --git a/packages/SystemUI/animation/build.gradle b/packages/SystemUI/animation/core/build.gradle
similarity index 81%
rename from packages/SystemUI/animation/build.gradle
rename to packages/SystemUI/animation/core/build.gradle
index 939455f..12637f4 100644
--- a/packages/SystemUI/animation/build.gradle
+++ b/packages/SystemUI/animation/core/build.gradle
@@ -5,8 +5,8 @@
 android {
     sourceSets {
         main {
-            java.srcDirs = ["${SYS_UI_DIR}/animation/src/com/android/systemui/surfaceeffects/"]
-            manifest.srcFile "${SYS_UI_DIR}/animation/AndroidManifest.xml"
+            java.srcDirs = ["${SYS_UI_DIR}/animation/core/src/com/android/systemui/surfaceeffects/"]
+            manifest.srcFile "${SYS_UI_DIR}/animation/core/AndroidManifest.xml"
         }
     }
 
diff --git a/packages/SystemUI/animation/res/anim/launch_dialog_enter.xml b/packages/SystemUI/animation/core/res/anim/launch_dialog_enter.xml
similarity index 100%
rename from packages/SystemUI/animation/res/anim/launch_dialog_enter.xml
rename to packages/SystemUI/animation/core/res/anim/launch_dialog_enter.xml
diff --git a/packages/SystemUI/animation/res/anim/launch_dialog_exit.xml b/packages/SystemUI/animation/core/res/anim/launch_dialog_exit.xml
similarity index 100%
rename from packages/SystemUI/animation/res/anim/launch_dialog_exit.xml
rename to packages/SystemUI/animation/core/res/anim/launch_dialog_exit.xml
diff --git a/packages/SystemUI/animation/res/values/ids.xml b/packages/SystemUI/animation/core/res/values/ids.xml
similarity index 100%
rename from packages/SystemUI/animation/res/values/ids.xml
rename to packages/SystemUI/animation/core/res/values/ids.xml
diff --git a/packages/SystemUI/animation/res/values/styles.xml b/packages/SystemUI/animation/core/res/values/styles.xml
similarity index 100%
rename from packages/SystemUI/animation/res/values/styles.xml
rename to packages/SystemUI/animation/core/res/values/styles.xml
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/AnimationFeatureFlags.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/AnimationFeatureFlags.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/AnimationFeatureFlags.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/AnimationFeatureFlags.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/DialogLaunchAnimator.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/DialogLaunchAnimator.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/Expandable.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/Expandable.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/FontInterpolator.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/FontInterpolator.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/FontVariationUtils.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/FontVariationUtils.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/LaunchAnimator.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/LaunchAnimator.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/LaunchableView.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/LaunchableView.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationDelegate.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/RemoteAnimationDelegate.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationDelegate.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/RemoteAnimationDelegate.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ShadeInterpolation.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/ShadeInterpolation.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/ShadeInterpolation.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/ShadeInterpolation.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/TextAnimator.kt
similarity index 93%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/TextAnimator.kt
index 8dc7495..dc54240 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/core/src/com/android/systemui/animation/TextAnimator.kt
@@ -42,9 +42,9 @@
                 return baseTypeface
             }
 
-            val axes = FontVariationAxis.fromFontVariationSettings(fVar)
-                ?.toMutableList()
-                ?: mutableListOf()
+            val axes =
+                FontVariationAxis.fromFontVariationSettings(fVar)?.toMutableList()
+                    ?: mutableListOf()
             axes.removeIf { !baseTypeface.isSupportedAxes(it.getOpenTypeTagValue()) }
             if (axes.isEmpty()) {
                 return baseTypeface
@@ -206,12 +206,14 @@
      *
      * Here is an example of font runs: "fin. 終わり"
      *
+     * ```
      * Characters :    f      i      n      .      _      終     わ     り
      * Code Points: \u0066 \u0069 \u006E \u002E \u0020 \u7D42 \u308F \u308A
      * Font Runs  : <-- Roboto-Regular.ttf          --><-- NotoSans-CJK.otf -->
      *                  runStart = 0, runLength = 5        runStart = 5, runLength = 3
      * Glyph IDs  :      194        48     7      8     4367   1039   1002
      * Glyph Index:       0          1     2      3       0      1      2
+     * ```
      *
      * In this example, the "fi" is converted into ligature form, thus the single glyph ID is
      * assigned for two characters, f and i.
@@ -243,22 +245,21 @@
     /**
      * Set text style with animation.
      *
-     * By passing -1 to weight, the view preserve the current weight.
-     * By passing -1 to textSize, the view preserve the current text size.
-     * Bu passing -1 to duration, the default text animation, 1000ms, is used.
-     * By passing false to animate, the text will be updated without animation.
+     * By passing -1 to weight, the view preserve the current weight. By passing -1 to textSize, the
+     * view preserve the current text size. Bu passing -1 to duration, the default text animation,
+     * 1000ms, is used. By passing false to animate, the text will be updated without animation.
      *
      * @param fvar an optional text fontVariationSettings.
      * @param textSize an optional font size.
-     * @param colors an optional colors array that must be the same size as numLines passed to
-     *               the TextInterpolator
+     * @param colors an optional colors array that must be the same size as numLines passed to the
+     *   TextInterpolator
      * @param strokeWidth an optional paint stroke width
      * @param animate an optional boolean indicating true for showing style transition as animation,
-     *                false for immediate style transition. True by default.
+     *   false for immediate style transition. True by default.
      * @param duration an optional animation duration in milliseconds. This is ignored if animate is
-     *                 false.
+     *   false.
      * @param interpolator an optional time interpolator. If null is passed, last set interpolator
-     *                     will be used. This is ignored if animate is false.
+     *   will be used. This is ignored if animate is false.
      */
     fun setTextStyle(
         fvar: String? = "",
@@ -324,7 +325,8 @@
     }
 
     /**
-     * Set text style with animation. Similar as
+     * Set text style with animation. Similar as fun setTextStyle( fvar: String? = "", textSize:
+     * ```
      * fun setTextStyle(
      *      fvar: String? = "",
      *      textSize: Float = -1f,
@@ -336,6 +338,7 @@
      *      delay: Long = 0,
      *      onAnimationEnd: Runnable? = null
      * )
+     * ```
      *
      * @param weight an optional style value for `wght` in fontVariationSettings.
      * @param width an optional style value for `wdth` in fontVariationSettings.
@@ -356,12 +359,13 @@
         delay: Long = 0,
         onAnimationEnd: Runnable? = null
     ) {
-        val fvar = fontVariationUtils.updateFontVariation(
-            weight = weight,
-            width = width,
-            opticalSize = opticalSize,
-            roundness = roundness,
-        )
+        val fvar =
+            fontVariationUtils.updateFontVariation(
+                weight = weight,
+                width = width,
+                opticalSize = opticalSize,
+                roundness = roundness,
+            )
         setTextStyle(
             fvar = fvar,
             textSize = textSize,
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/TextInterpolator.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/TextInterpolator.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
similarity index 92%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
index 00d9056..a9c53d1 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
+++ b/packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
@@ -196,9 +196,9 @@
          *
          * @param includeFadeIn true if the animator should also fade in the view and child views.
          * @param fadeInInterpolator the interpolator to use when fading in the view. Unused if
-         *     [includeFadeIn] is false.
-         * @param onAnimationEnd an optional runnable that will be run once the animation
-         *    finishes successfully. Will not be run if the animation is cancelled.
+         *   [includeFadeIn] is false.
+         * @param onAnimationEnd an optional runnable that will be run once the animation finishes
+         *   successfully. Will not be run if the animation is cancelled.
          */
         @JvmOverloads
         fun animateAddition(
@@ -241,7 +241,10 @@
                 // First, fade in the container view
                 val containerDuration = duration / 6
                 createAndStartFadeInAnimator(
-                    rootView, containerDuration, startDelay = 0, interpolator = fadeInInterpolator
+                    rootView,
+                    containerDuration,
+                    startDelay = 0,
+                    interpolator = fadeInInterpolator
                 )
 
                 // Then, fade in the child views
@@ -397,7 +400,7 @@
          * included by specifying [includeMargins].
          *
          * @param onAnimationEnd an optional runnable that will be run once the animation finishes
-         *    successfully. Will not be run if the animation is cancelled.
+         *   successfully. Will not be run if the animation is cancelled.
          */
         @JvmOverloads
         fun animateRemoval(
@@ -616,6 +619,7 @@
          * not newly introduced margins are included.
          *
          * Base case
+         *
          * ```
          *     1) origin=TOP
          *         x---------x    x---------x    x---------x    x---------x    x---------x
@@ -636,9 +640,11 @@
          *                                         x-----x       x-------x     |         |
          *                                                                     x---------x
          * ```
+         *
          * In case the start and end values differ in the direction of the origin, and
          * [ignorePreviousValues] is false, the previous values are used and a translation is
          * included in addition to the view expansion.
+         *
          * ```
          *     origin=TOP_LEFT - (0,0,0,0) -> (30,30,70,70)
          *         x
@@ -777,8 +783,7 @@
             includeMargins: Boolean = false,
         ): Map<Bound, Int> {
             val marginAdjustment =
-                if (includeMargins &&
-                    (rootView.layoutParams is ViewGroup.MarginLayoutParams)) {
+                if (includeMargins && (rootView.layoutParams is ViewGroup.MarginLayoutParams)) {
                     val marginLp = rootView.layoutParams as ViewGroup.MarginLayoutParams
                     DimenHolder(
                         left = marginLp.leftMargin,
@@ -786,9 +791,9 @@
                         right = marginLp.rightMargin,
                         bottom = marginLp.bottomMargin
                     )
-            } else {
-                DimenHolder(0, 0, 0, 0)
-            }
+                } else {
+                    DimenHolder(0, 0, 0, 0)
+                }
 
             // These are the end values to use *if* this bound is part of the destination.
             val endLeft = left - marginAdjustment.left
@@ -805,60 +810,69 @@
             //  - If destination=BOTTOM_LEFT, then endBottom == endTop AND endLeft == endRight.
 
             return when (destination) {
-                Hotspot.TOP -> mapOf(
-                    Bound.TOP to endTop,
-                    Bound.BOTTOM to endTop,
-                    Bound.LEFT to left,
-                    Bound.RIGHT to right,
-                )
-                Hotspot.TOP_RIGHT -> mapOf(
-                    Bound.TOP to endTop,
-                    Bound.BOTTOM to endTop,
-                    Bound.RIGHT to endRight,
-                    Bound.LEFT to endRight,
-                )
-                Hotspot.RIGHT -> mapOf(
-                    Bound.RIGHT to endRight,
-                    Bound.LEFT to endRight,
-                    Bound.TOP to top,
-                    Bound.BOTTOM to bottom,
-                )
-                Hotspot.BOTTOM_RIGHT -> mapOf(
-                    Bound.BOTTOM to endBottom,
-                    Bound.TOP to endBottom,
-                    Bound.RIGHT to endRight,
-                    Bound.LEFT to endRight,
-                )
-                Hotspot.BOTTOM -> mapOf(
-                    Bound.BOTTOM to endBottom,
-                    Bound.TOP to endBottom,
-                    Bound.LEFT to left,
-                    Bound.RIGHT to right,
-                )
-                Hotspot.BOTTOM_LEFT -> mapOf(
-                    Bound.BOTTOM to endBottom,
-                    Bound.TOP to endBottom,
-                    Bound.LEFT to endLeft,
-                    Bound.RIGHT to endLeft,
-                )
-                Hotspot.LEFT -> mapOf(
-                    Bound.LEFT to endLeft,
-                    Bound.RIGHT to endLeft,
-                    Bound.TOP to top,
-                    Bound.BOTTOM to bottom,
-                )
-                Hotspot.TOP_LEFT -> mapOf(
-                    Bound.TOP to endTop,
-                    Bound.BOTTOM to endTop,
-                    Bound.LEFT to endLeft,
-                    Bound.RIGHT to endLeft,
-                )
-                Hotspot.CENTER -> mapOf(
-                    Bound.LEFT to (endLeft + endRight) / 2,
-                    Bound.RIGHT to (endLeft + endRight) / 2,
-                    Bound.TOP to (endTop + endBottom) / 2,
-                    Bound.BOTTOM to (endTop + endBottom) / 2,
-                )
+                Hotspot.TOP ->
+                    mapOf(
+                        Bound.TOP to endTop,
+                        Bound.BOTTOM to endTop,
+                        Bound.LEFT to left,
+                        Bound.RIGHT to right,
+                    )
+                Hotspot.TOP_RIGHT ->
+                    mapOf(
+                        Bound.TOP to endTop,
+                        Bound.BOTTOM to endTop,
+                        Bound.RIGHT to endRight,
+                        Bound.LEFT to endRight,
+                    )
+                Hotspot.RIGHT ->
+                    mapOf(
+                        Bound.RIGHT to endRight,
+                        Bound.LEFT to endRight,
+                        Bound.TOP to top,
+                        Bound.BOTTOM to bottom,
+                    )
+                Hotspot.BOTTOM_RIGHT ->
+                    mapOf(
+                        Bound.BOTTOM to endBottom,
+                        Bound.TOP to endBottom,
+                        Bound.RIGHT to endRight,
+                        Bound.LEFT to endRight,
+                    )
+                Hotspot.BOTTOM ->
+                    mapOf(
+                        Bound.BOTTOM to endBottom,
+                        Bound.TOP to endBottom,
+                        Bound.LEFT to left,
+                        Bound.RIGHT to right,
+                    )
+                Hotspot.BOTTOM_LEFT ->
+                    mapOf(
+                        Bound.BOTTOM to endBottom,
+                        Bound.TOP to endBottom,
+                        Bound.LEFT to endLeft,
+                        Bound.RIGHT to endLeft,
+                    )
+                Hotspot.LEFT ->
+                    mapOf(
+                        Bound.LEFT to endLeft,
+                        Bound.RIGHT to endLeft,
+                        Bound.TOP to top,
+                        Bound.BOTTOM to bottom,
+                    )
+                Hotspot.TOP_LEFT ->
+                    mapOf(
+                        Bound.TOP to endTop,
+                        Bound.BOTTOM to endTop,
+                        Bound.LEFT to endLeft,
+                        Bound.RIGHT to endLeft,
+                    )
+                Hotspot.CENTER ->
+                    mapOf(
+                        Bound.LEFT to (endLeft + endRight) / 2,
+                        Bound.RIGHT to (endLeft + endRight) / 2,
+                        Bound.TOP to (endTop + endBottom) / 2,
+                        Bound.BOTTOM to (endTop + endBottom) / 2,
+                    )
             }
         }
 
@@ -1083,11 +1097,13 @@
             animator.startDelay = startDelay
             animator.duration = duration
             animator.interpolator = interpolator
-            animator.addListener(object : AnimatorListenerAdapter() {
-                override fun onAnimationEnd(animation: Animator) {
-                    view.setTag(R.id.tag_alpha_animator, null /* tag */)
+            animator.addListener(
+                object : AnimatorListenerAdapter() {
+                    override fun onAnimationEnd(animation: Animator) {
+                        view.setTag(R.id.tag_alpha_animator, null /* tag */)
+                    }
                 }
-            })
+            )
 
             (view.getTag(R.id.tag_alpha_animator) as? ObjectAnimator)?.cancel()
             view.setTag(R.id.tag_alpha_animator, animator)
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewRootSync.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewRootSync.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackAnimationSpec.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackAnimationSpec.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackTransformation.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackTransformation.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/back/BackTransformation.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackTransformation.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableImageView.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableImageView.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableImageView.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableImageView.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableTextView.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableTextView.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableTextView.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableTextView.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/util/AnimatorExtensions.kt b/packages/SystemUI/animation/core/src/com/android/systemui/util/AnimatorExtensions.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/util/AnimatorExtensions.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/util/AnimatorExtensions.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt b/packages/SystemUI/animation/core/src/com/android/systemui/util/Dialog.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/util/Dialog.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/util/Dimension.kt b/packages/SystemUI/animation/core/src/com/android/systemui/util/Dimension.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/util/Dimension.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/util/Dimension.kt
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 d83f3aa..249b3e1 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
@@ -74,7 +74,7 @@
 ) {
     val currentScene: SceneKey by
         viewModel.currentScene
-            .transform<CommunalSceneKey, SceneKey> { value -> value.toTransitionSceneKey() }
+            .transform { value -> emit(value.toTransitionSceneKey()) }
             .collectAsState(TransitionSceneKey.Blank)
     val sceneTransitionLayoutState = remember { SceneTransitionLayoutState(currentScene) }
     // Don't show hub mode UI if keyguard is present. This is important since we're in the shade,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 5dc1079..a85d9bf 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -16,6 +16,7 @@
 
 package com.android.compose.animation.scene
 
+import android.graphics.Picture
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
@@ -66,12 +67,21 @@
      * The movable content of this element, if this element is composed using
      * [SceneScope.MovableElement].
      */
-    val movableContent by
-        // This is only accessed from the composition (main) thread, so no need to use the default
-        // lock of lazy {} to synchronize.
-        lazy(mode = LazyThreadSafetyMode.NONE) {
-            movableContentOf { content: @Composable () -> Unit -> content() }
-        }
+    private var _movableContent: (@Composable (@Composable () -> Unit) -> Unit)? = null
+    val movableContent: @Composable (@Composable () -> Unit) -> Unit
+        get() =
+            _movableContent
+                ?: movableContentOf { content: @Composable () -> Unit -> content() }
+                    .also { _movableContent = it }
+
+    /**
+     * The [Picture] to which we save the last drawing commands of this element, if it is movable.
+     * This is necessary because the content of this element might not be composed in the scene it
+     * should currently be drawn.
+     */
+    private var _picture: Picture? = null
+    val picture: Picture
+        get() = _picture ?: Picture().also { _picture = it }
 
     override fun toString(): String {
         return "Element(key=$key)"
@@ -235,18 +245,16 @@
     }
 
     override fun ContentDrawScope.draw() {
-        if (shouldDrawElement(layoutImpl, scene, element)) {
-            val drawScale = getDrawScale(layoutImpl, element, scene, sceneValues)
-            if (drawScale == Scale.Default) {
-                drawContent()
-            } else {
-                scale(
-                    drawScale.scaleX,
-                    drawScale.scaleY,
-                    if (drawScale.pivot.isUnspecified) center else drawScale.pivot,
-                ) {
-                    this@draw.drawContent()
-                }
+        val drawScale = getDrawScale(layoutImpl, element, scene, sceneValues)
+        if (drawScale == Scale.Default) {
+            drawContent()
+        } else {
+            scale(
+                drawScale.scaleX,
+                drawScale.scaleY,
+                if (drawScale.pivot.isUnspecified) center else drawScale.pivot,
+            ) {
+                this@draw.drawContent()
             }
         }
     }
@@ -516,7 +524,7 @@
     with(placementScope) {
         // Update the offset (relative to the SceneTransitionLayout) this element has in this scene
         // when idle.
-        val coords = coordinates!!
+        val coords = coordinates ?: error("Element ${element.key} does not have any coordinates")
         val targetOffsetInScene = lookaheadScopeCoordinates.localLookaheadPositionOf(coords)
         if (targetOffsetInScene != sceneValues.targetOffset) {
             // TODO(b/290930950): Better handle when this changes to avoid instant offset jumps.
@@ -545,6 +553,13 @@
         lastSharedValues.offset = targetOffset
         lastValues.offset = targetOffset
 
+        // No need to place the element in this scene if we don't want to draw it anyways. Note that
+        // it's still important to compute the target offset and update lastValues, otherwise it
+        // will be out of date.
+        if (!shouldDrawElement(layoutImpl, scene, element)) {
+            return
+        }
+
         val offset = (targetOffset - currentOffset).round()
         if (isElementOpaque(layoutImpl, element, scene, sceneValues)) {
             // TODO(b/291071158): Call placeWithLayer() if offset != IntOffset.Zero and size is not
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
index 306f276..49df2f6 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
@@ -16,7 +16,6 @@
 
 package com.android.compose.animation.scene
 
-import android.graphics.Picture
 import android.util.Log
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Spacer
@@ -60,7 +59,7 @@
         // The [Picture] to which we save the last drawing commands of this element. This is
         // necessary because the content of this element might not be composed in this scene, in
         // which case we still need to draw it.
-        val picture = remember { Picture() }
+        val picture = element.picture
 
         // Whether we should compose the movable element here. The scene picker logic to know in
         // which scene we should compose/draw a movable element might depend on the current
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 6a7a3a0..30e50a9 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
@@ -34,6 +34,7 @@
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.zIndex
+import com.android.compose.animation.scene.modifiers.noResizeDuringTransitions
 
 /** A scene in a [SceneTransitionLayout]. */
 @Stable
@@ -152,4 +153,8 @@
         bounds: ElementKey,
         shape: Shape
     ): Modifier = punchHole(layoutImpl, element, bounds, shape)
+
+    override fun Modifier.noResizeDuringTransitions(): Modifier {
+        return noResizeDuringTransitions(layoutState = layoutImpl.state)
+    }
 }
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 3608e37..5eb339e 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
@@ -65,11 +65,11 @@
     SceneTransitionLayoutForTesting(
         currentScene,
         onChangeScene,
+        modifier,
         transitions,
         state,
         edgeDetector,
         transitionInterceptionThreshold,
-        modifier,
         onLayoutImpl = null,
         scenes,
     )
@@ -205,6 +205,12 @@
      * the result.
      */
     fun Modifier.punchHole(element: ElementKey, bounds: ElementKey, shape: Shape): Modifier
+
+    /**
+     * Don't resize during transitions. This can for instance be used to make sure that scrollable
+     * lists keep a constant size during transitions even if its elements are growing/shrinking.
+     */
+    fun Modifier.noResizeDuringTransitions(): Modifier
 }
 
 // TODO(b/291053742): Add animateSharedValueAsState(targetValue) without any ValueKey and ElementKey
@@ -257,12 +263,12 @@
 internal fun SceneTransitionLayoutForTesting(
     currentScene: SceneKey,
     onChangeScene: (SceneKey) -> Unit,
-    transitions: SceneTransitions,
-    state: SceneTransitionLayoutState,
-    edgeDetector: EdgeDetector,
-    transitionInterceptionThreshold: Float,
-    modifier: Modifier,
-    onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)?,
+    modifier: Modifier = Modifier,
+    transitions: SceneTransitions = transitions {},
+    state: SceneTransitionLayoutState = remember { SceneTransitionLayoutState(currentScene) },
+    edgeDetector: EdgeDetector = DefaultEdgeDetector,
+    transitionInterceptionThreshold: Float = 0f,
+    onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)? = null,
     scenes: SceneTransitionLayoutScope.() -> Unit,
 ) {
     val density = LocalDensity.current
@@ -280,6 +286,10 @@
             .also { onLayoutImpl?.invoke(it) }
     }
 
+    // TODO(b/317014852): Move this into the SideEffect {} again once STLImpl.scenes is not a
+    // SnapshotStateMap anymore.
+    layoutImpl.updateScenes(scenes)
+
     val targetSceneChannel = remember { Channel<SceneKey>(Channel.CONFLATED) }
     SideEffect {
         if (state != layoutImpl.state) {
@@ -293,7 +303,6 @@
         (state as SceneTransitionLayoutStateImpl).transitions = transitions
         layoutImpl.density = density
         layoutImpl.edgeDetector = edgeDetector
-        layoutImpl.updateScenes(scenes)
 
         state.transitions = transitions
 
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 c99c325..45e1a0f 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
@@ -46,7 +46,12 @@
     builder: SceneTransitionLayoutScope.() -> Unit,
     coroutineScope: CoroutineScope,
 ) {
-    internal val scenes = mutableMapOf<SceneKey, Scene>()
+    /**
+     * The map of [Scene]s.
+     *
+     * TODO(b/317014852): Make this a normal MutableMap instead.
+     */
+    internal val scenes = SnapshotStateMap<SceneKey, Scene>()
 
     /**
      * The map of [Element]s.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/modifiers/Size.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/modifiers/Size.kt
new file mode 100644
index 0000000..bd36cb8
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/modifiers/Size.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.compose.animation.scene.modifiers
+
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.intermediateLayout
+import androidx.compose.ui.unit.Constraints
+import com.android.compose.animation.scene.SceneTransitionLayoutState
+
+@OptIn(ExperimentalComposeUiApi::class)
+internal fun Modifier.noResizeDuringTransitions(layoutState: SceneTransitionLayoutState): Modifier {
+    return intermediateLayout { measurable, constraints ->
+        if (layoutState.currentTransition == null) {
+            return@intermediateLayout measurable.measure(constraints).run {
+                layout(width, height) { place(0, 0) }
+            }
+        }
+
+        // Make sure that this layout node has the same size than when we are at rest.
+        val sizeAtRest = lookaheadSize
+        measurable.measure(Constraints.fixed(sizeAtRest.width, sizeAtRest.height)).run {
+            layout(width, height) { place(0, 0) }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 439dc00..da5a0a0 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.compose.animation.scene
 
+import androidx.compose.animation.core.LinearEasing
 import androidx.compose.animation.core.tween
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.layout.Box
@@ -30,16 +31,21 @@
 import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.layout.intermediateLayout
+import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.DpOffset
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.test.subjects.DpOffsetSubject
+import com.android.compose.test.subjects.assertThat
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
@@ -116,7 +122,13 @@
             toSceneContent = {
                 Box(Modifier.size(layoutSize)) {
                     // Shared element.
-                    Element(TestElements.Foo, elementSize, elementOffset)
+                    Element(
+                        TestElements.Foo,
+                        elementSize,
+                        elementOffset,
+                        onLayout = { fooLayouts++ },
+                        onPlacement = { fooPlacements++ },
+                    )
                 }
             },
             transition = {
@@ -127,21 +139,25 @@
                 scaleSize(TestElements.Bar, width = 1f, height = 1f)
             },
         ) {
-            var numberOfLayoutsAfterOneAnimationFrame = 0
-            var numberOfPlacementsAfterOneAnimationFrame = 0
+            var fooLayoutsAfterOneAnimationFrame = 0
+            var fooPlacementsAfterOneAnimationFrame = 0
+            var barLayoutsAfterOneAnimationFrame = 0
+            var barPlacementsAfterOneAnimationFrame = 0
 
             fun assertNumberOfLayoutsAndPlacements() {
-                assertThat(fooLayouts).isEqualTo(numberOfLayoutsAfterOneAnimationFrame)
-                assertThat(fooPlacements).isEqualTo(numberOfPlacementsAfterOneAnimationFrame)
-                assertThat(barLayouts).isEqualTo(numberOfLayoutsAfterOneAnimationFrame)
-                assertThat(barPlacements).isEqualTo(numberOfPlacementsAfterOneAnimationFrame)
+                assertThat(fooLayouts).isEqualTo(fooLayoutsAfterOneAnimationFrame)
+                assertThat(fooPlacements).isEqualTo(fooPlacementsAfterOneAnimationFrame)
+                assertThat(barLayouts).isEqualTo(barLayoutsAfterOneAnimationFrame)
+                assertThat(barPlacements).isEqualTo(barPlacementsAfterOneAnimationFrame)
             }
 
             at(16) {
                 // Capture the number of layouts and placements that happened after 1 animation
                 // frame.
-                numberOfLayoutsAfterOneAnimationFrame = fooLayouts
-                numberOfPlacementsAfterOneAnimationFrame = fooPlacements
+                fooLayoutsAfterOneAnimationFrame = fooLayouts
+                fooPlacementsAfterOneAnimationFrame = fooPlacements
+                barLayoutsAfterOneAnimationFrame = barLayouts
+                barPlacementsAfterOneAnimationFrame = barPlacements
             }
             repeat(nFrames - 2) { i ->
                 // Ensure that all animation frames (except the final one) don't relayout or replace
@@ -187,7 +203,13 @@
             toSceneContent = {
                 Box(Modifier.size(layoutSize)) {
                     // Shared element.
-                    Element(TestElements.Foo, elementSize, offset = 20.dp)
+                    Element(
+                        TestElements.Foo,
+                        elementSize,
+                        offset = 20.dp,
+                        onLayout = { fooLayouts++ },
+                        onPlacement = { fooPlacements++ },
+                    )
                 }
             },
             transition = {
@@ -198,25 +220,30 @@
                 scaleSize(TestElements.Bar, width = 1f, height = 1f)
             },
         ) {
-            var numberOfLayoutsAfterOneAnimationFrame = 0
-            var lastNumberOfPlacements = 0
+            var fooLayoutsAfterOneAnimationFrame = 0
+            var barLayoutsAfterOneAnimationFrame = 0
+            var lastFooPlacements = 0
+            var lastBarPlacements = 0
 
             fun assertNumberOfLayoutsAndPlacements() {
                 // The number of layouts have not changed.
-                assertThat(fooLayouts).isEqualTo(numberOfLayoutsAfterOneAnimationFrame)
-                assertThat(barLayouts).isEqualTo(numberOfLayoutsAfterOneAnimationFrame)
+                assertThat(fooLayouts).isEqualTo(fooLayoutsAfterOneAnimationFrame)
+                assertThat(barLayouts).isEqualTo(barLayoutsAfterOneAnimationFrame)
 
                 // The number of placements have increased.
-                assertThat(fooPlacements).isGreaterThan(lastNumberOfPlacements)
-                assertThat(barPlacements).isGreaterThan(lastNumberOfPlacements)
-                lastNumberOfPlacements = fooPlacements
+                assertThat(fooPlacements).isGreaterThan(lastFooPlacements)
+                assertThat(barPlacements).isGreaterThan(lastBarPlacements)
+                lastFooPlacements = fooPlacements
+                lastBarPlacements = barPlacements
             }
 
             at(16) {
                 // Capture the number of layouts and placements that happened after 1 animation
                 // frame.
-                numberOfLayoutsAfterOneAnimationFrame = fooLayouts
-                lastNumberOfPlacements = fooPlacements
+                fooLayoutsAfterOneAnimationFrame = fooLayouts
+                barLayoutsAfterOneAnimationFrame = barLayouts
+                lastFooPlacements = fooPlacements
+                lastBarPlacements = barPlacements
             }
             repeat(nFrames - 2) { i ->
                 // Ensure that all animation frames (except the final one) only replaced the
@@ -238,11 +265,6 @@
             SceneTransitionLayoutForTesting(
                 currentScene = currentScene,
                 onChangeScene = { currentScene = it },
-                transitions = remember { transitions {} },
-                state = remember { SceneTransitionLayoutState(currentScene) },
-                edgeDetector = DefaultEdgeDetector,
-                modifier = Modifier,
-                transitionInterceptionThreshold = 0f,
                 onLayoutImpl = { nullableLayoutImpl = it },
             ) {
                 scene(TestScenes.SceneA) { /* Nothing */}
@@ -408,11 +430,6 @@
             SceneTransitionLayoutForTesting(
                 currentScene = TestScenes.SceneA,
                 onChangeScene = {},
-                transitions = remember { transitions {} },
-                state = remember { SceneTransitionLayoutState(TestScenes.SceneA) },
-                edgeDetector = DefaultEdgeDetector,
-                modifier = Modifier,
-                transitionInterceptionThreshold = 0f,
                 onLayoutImpl = { nullableLayoutImpl = it },
             ) {
                 scene(TestScenes.SceneA) { Box(Modifier.element(key)) }
@@ -463,11 +480,6 @@
             SceneTransitionLayoutForTesting(
                 currentScene = TestScenes.SceneA,
                 onChangeScene = {},
-                transitions = remember { transitions {} },
-                state = remember { SceneTransitionLayoutState(TestScenes.SceneA) },
-                edgeDetector = DefaultEdgeDetector,
-                modifier = Modifier,
-                transitionInterceptionThreshold = 0f,
                 onLayoutImpl = { nullableLayoutImpl = it },
             ) {
                 scene(TestScenes.SceneA) {
@@ -553,4 +565,86 @@
             after { assertThat(fooCompositions).isEqualTo(1) }
         }
     }
+
+    @Test
+    fun sharedElementOffsetIsUpdatedEvenWhenNotPlaced() {
+        var nullableLayoutImpl: SceneTransitionLayoutImpl? = null
+        var density: Density? = null
+
+        fun layoutImpl() = nullableLayoutImpl ?: error("nullableLayoutImpl was not set")
+
+        fun density() = density ?: error("density was not set")
+
+        fun Offset.toDpOffset() = with(density()) { DpOffset(x.toDp(), y.toDp()) }
+
+        fun foo() = layoutImpl().elements[TestElements.Foo] ?: error("Foo not in elements map")
+
+        fun Element.lastSharedOffset() = lastSharedValues.offset.toDpOffset()
+
+        fun Element.lastOffsetIn(scene: SceneKey) =
+            (sceneValues[scene] ?: error("$scene not in sceneValues map"))
+                .lastValues
+                .offset
+                .toDpOffset()
+
+        rule.testTransition(
+            from = TestScenes.SceneA,
+            to = TestScenes.SceneB,
+            transitionLayout = { currentScene, onChangeScene ->
+                density = LocalDensity.current
+
+                SceneTransitionLayoutForTesting(
+                    currentScene = currentScene,
+                    onChangeScene = onChangeScene,
+                    onLayoutImpl = { nullableLayoutImpl = it },
+                    transitions =
+                        transitions {
+                            from(TestScenes.SceneA, to = TestScenes.SceneB) {
+                                spec = tween(durationMillis = 4 * 16, easing = LinearEasing)
+                            }
+                        }
+                ) {
+                    scene(TestScenes.SceneA) { Box(Modifier.element(TestElements.Foo)) }
+                    scene(TestScenes.SceneB) {
+                        Box(Modifier.offset(x = 40.dp, y = 80.dp).element(TestElements.Foo))
+                    }
+                }
+            }
+        ) {
+            val tolerance = DpOffsetSubject.DefaultTolerance
+
+            before {
+                val expected = DpOffset(0.dp, 0.dp)
+                assertThat(foo().lastSharedOffset()).isWithin(tolerance).of(expected)
+                assertThat(foo().lastOffsetIn(TestScenes.SceneA)).isWithin(tolerance).of(expected)
+            }
+
+            at(16) {
+                val expected = DpOffset(10.dp, 20.dp)
+                assertThat(foo().lastSharedOffset()).isWithin(tolerance).of(expected)
+                assertThat(foo().lastOffsetIn(TestScenes.SceneA)).isWithin(tolerance).of(expected)
+                assertThat(foo().lastOffsetIn(TestScenes.SceneB)).isWithin(tolerance).of(expected)
+            }
+
+            at(32) {
+                val expected = DpOffset(20.dp, 40.dp)
+                assertThat(foo().lastSharedOffset()).isWithin(tolerance).of(expected)
+                assertThat(foo().lastOffsetIn(TestScenes.SceneA)).isWithin(tolerance).of(expected)
+                assertThat(foo().lastOffsetIn(TestScenes.SceneB)).isWithin(tolerance).of(expected)
+            }
+
+            at(48) {
+                val expected = DpOffset(30.dp, 60.dp)
+                assertThat(foo().lastSharedOffset()).isWithin(tolerance).of(expected)
+                assertThat(foo().lastOffsetIn(TestScenes.SceneA)).isWithin(tolerance).of(expected)
+                assertThat(foo().lastOffsetIn(TestScenes.SceneB)).isWithin(tolerance).of(expected)
+            }
+
+            after {
+                val expected = DpOffset(40.dp, 80.dp)
+                assertThat(foo().lastSharedOffset()).isWithin(tolerance).of(expected)
+                assertThat(foo().lastOffsetIn(TestScenes.SceneB)).isWithin(tolerance).of(expected)
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
index 83af630..3cd65cd 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
@@ -30,6 +30,7 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
 import androidx.compose.ui.test.hasParent
 import androidx.compose.ui.test.hasText
 import androidx.compose.ui.test.junit4.createComposeRule
@@ -92,15 +93,15 @@
             }
 
             at(32) {
-                // In the middle of the transition, there are 2 copies of the counter: the previous
-                // one from scene A (equal to 3) and the new one from scene B (equal to 0).
+                // In the middle of the transition, 2 copies of the counter are composed but only
+                // the one in scene B is placed/drawn.
                 rule
                     .onNode(
                         hasText("count: 3") and
                             hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneA))
                     )
-                    .assertIsDisplayed()
-                    .assertSizeIsEqualTo(75.dp, 75.dp)
+                    .assertExists()
+                    .assertIsNotDisplayed()
 
                 rule
                     .onNode(
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index 321cf63..ebbd500 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -40,9 +40,7 @@
 import androidx.compose.ui.test.assertPositionInRootIsEqualTo
 import androidx.compose.ui.test.assertWidthIsEqualTo
 import androidx.compose.ui.test.junit4.createAndroidComposeRule
-import androidx.compose.ui.test.onAllNodesWithTag
 import androidx.compose.ui.test.onChild
-import androidx.compose.ui.test.onFirst
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.unit.Dp
@@ -251,9 +249,9 @@
         // Advance to the middle of the animation.
         rule.mainClock.advanceTimeBy(TestTransitionDuration / 2)
 
-        // We need to use onAllNodesWithTag().onFirst() here given that shared elements are
-        // composed and laid out in both scenes (but drawn only in one).
-        sharedFoo = rule.onAllNodesWithTag(TestElements.Foo.testTag).onFirst()
+        // Foo is shared between Scene A and Scene B, and is therefore placed/drawn in Scene B given
+        // that B has a higher zIndex than A.
+        sharedFoo = rule.onNode(isElement(TestElements.Foo, TestScenes.SceneB))
 
         // In scene B, foo is at the top start (x = 0, y = 0) of the layout and has a size of
         // 100.dp. We pause at the middle of the transition, so it should now be 75.dp given that we
@@ -287,7 +285,7 @@
         val expectedLeft = 0.dp
         val expectedSize = 100.dp + (150.dp - 100.dp) * interpolatedProgress
 
-        sharedFoo = rule.onAllNodesWithTag(TestElements.Foo.testTag).onFirst()
+        sharedFoo = rule.onNode(isElement(TestElements.Foo, TestScenes.SceneC))
         assertThat((layoutState.transitionState as TransitionState.Transition).progress)
             .isEqualTo(interpolatedProgress)
         sharedFoo.assertWidthIsEqualTo(expectedSize)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/modifiers/SizeTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/modifiers/SizeTest.kt
new file mode 100644
index 0000000..2c159d1
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/modifiers/SizeTest.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.compose.animation.scene.modifiers
+
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestElements
+import com.android.compose.animation.scene.element
+import com.android.compose.animation.scene.testTransition
+import com.android.compose.test.assertSizeIsEqualTo
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SizeTest {
+    @get:Rule val rule = createComposeRule()
+
+    @Test
+    fun noResizeDuringTransitions() {
+        // The tag for the parent of the shared Foo element.
+        val parentTag = "parent"
+
+        rule.testTransition(
+            fromSceneContent = { Box(Modifier.element(TestElements.Foo).size(100.dp)) },
+            toSceneContent = {
+                // Don't resize the parent of Foo during transitions so that it's always the same
+                // size as when there is no transition (200dp).
+                Box(Modifier.noResizeDuringTransitions().testTag(parentTag)) {
+                    Box(Modifier.element(TestElements.Foo).size(200.dp))
+                }
+            },
+            transition = { spec = tween(durationMillis = 4 * 16, easing = LinearEasing) },
+        ) {
+            at(16) { rule.onNodeWithTag(parentTag).assertSizeIsEqualTo(200.dp, 200.dp) }
+            at(32) { rule.onNodeWithTag(parentTag).assertSizeIsEqualTo(200.dp, 200.dp) }
+            at(48) { rule.onNodeWithTag(parentTag).assertSizeIsEqualTo(200.dp, 200.dp) }
+            after { rule.onNodeWithTag(parentTag).assertSizeIsEqualTo(200.dp, 200.dp) }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
index e94eff3..8001f41 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
@@ -23,6 +23,7 @@
 import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.size
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.assertIsNotDisplayed
 import androidx.compose.ui.test.assertPositionInRootIsEqualTo
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.unit.dp
@@ -33,7 +34,6 @@
 import com.android.compose.animation.scene.inScene
 import com.android.compose.animation.scene.testTransition
 import com.android.compose.test.assertSizeIsEqualTo
-import com.android.compose.test.onEach
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -63,28 +63,34 @@
                 onElement(TestElements.Foo).assertSizeIsEqualTo(20.dp, 80.dp)
             }
             at(0) {
-                onSharedElement(TestElements.Foo).onEach {
-                    assertPositionInRootIsEqualTo(10.dp, 50.dp)
-                    assertSizeIsEqualTo(20.dp, 80.dp)
-                }
+                // Shared elements are by default placed and drawn only in the scene with highest
+                // zIndex.
+                onElement(TestElements.Foo, TestScenes.SceneA).assertIsNotDisplayed()
+
+                onElement(TestElements.Foo, TestScenes.SceneB)
+                    .assertPositionInRootIsEqualTo(10.dp, 50.dp)
+                    .assertSizeIsEqualTo(20.dp, 80.dp)
             }
             at(16) {
-                onSharedElement(TestElements.Foo).onEach {
-                    assertPositionInRootIsEqualTo(20.dp, 55.dp)
-                    assertSizeIsEqualTo(17.5.dp, 70.dp)
-                }
+                onElement(TestElements.Foo, TestScenes.SceneA).assertIsNotDisplayed()
+
+                onElement(TestElements.Foo, TestScenes.SceneB)
+                    .assertPositionInRootIsEqualTo(20.dp, 55.dp)
+                    .assertSizeIsEqualTo(17.5.dp, 70.dp)
             }
             at(32) {
-                onSharedElement(TestElements.Foo).onEach {
-                    assertPositionInRootIsEqualTo(30.dp, 60.dp)
-                    assertSizeIsEqualTo(15.dp, 60.dp)
-                }
+                onElement(TestElements.Foo, TestScenes.SceneA).assertIsNotDisplayed()
+
+                onElement(TestElements.Foo, TestScenes.SceneB)
+                    .assertPositionInRootIsEqualTo(30.dp, 60.dp)
+                    .assertSizeIsEqualTo(15.dp, 60.dp)
             }
             at(48) {
-                onSharedElement(TestElements.Foo).onEach {
-                    assertPositionInRootIsEqualTo(40.dp, 65.dp)
-                    assertSizeIsEqualTo(12.5.dp, 50.dp)
-                }
+                onElement(TestElements.Foo, TestScenes.SceneA).assertIsNotDisplayed()
+
+                onElement(TestElements.Foo, TestScenes.SceneB)
+                    .assertPositionInRootIsEqualTo(40.dp, 65.dp)
+                    .assertSizeIsEqualTo(12.5.dp, 50.dp)
             }
             after {
                 onElement(TestElements.Foo).assertPositionInRootIsEqualTo(50.dp, 70.dp)
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestMatchers.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestMatchers.kt
new file mode 100644
index 0000000..e743c78
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestMatchers.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.compose.animation.scene
+
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.hasParent
+import androidx.compose.ui.test.hasTestTag
+
+/** A [SemanticsMatcher] that matches [element], optionally restricted to scene [scene]. */
+fun isElement(element: ElementKey, scene: SceneKey? = null): SemanticsMatcher {
+    return if (scene == null) {
+        hasTestTag(element.testTag)
+    } else {
+        hasTestTag(element.testTag) and hasParent(hasTestTag(scene.testTag))
+    }
+}
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
index 06de296..6d6d575 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
@@ -21,13 +21,8 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.test.SemanticsMatcher
 import androidx.compose.ui.test.SemanticsNodeInteraction
-import androidx.compose.ui.test.SemanticsNodeInteractionCollection
-import androidx.compose.ui.test.hasParent
-import androidx.compose.ui.test.hasTestTag
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
-import androidx.compose.ui.test.onAllNodesWithTag
 
 @DslMarker annotation class TransitionTestDsl
 
@@ -62,23 +57,15 @@
 
 @TransitionTestDsl
 interface TransitionTestAssertionScope {
-    fun isElement(element: ElementKey, scene: SceneKey? = null): SemanticsMatcher
-
     /**
      * Assert on [element].
      *
      * Note that presence/value assertions on the returned [SemanticsNodeInteraction] will fail if 0
      * or more than 1 elements matched [element]. If you need to assert on a shared element that
-     * will be present multiple times in the layout during transitions, either specify the [scene]
-     * in which you are matching or use [onSharedElement] instead.
+     * will be present multiple times in the layout during transitions, specify the [scene] in which
+     * you are matching.
      */
     fun onElement(element: ElementKey, scene: SceneKey? = null): SemanticsNodeInteraction
-
-    /**
-     * Assert on a shared [element]. This will throw if [element] is not shared and present only in
-     * one scene during a transition.
-     */
-    fun onSharedElement(element: ElementKey): SemanticsNodeInteractionCollection
 }
 
 /**
@@ -131,29 +118,12 @@
     val test = transitionTest(builder)
     val assertionScope =
         object : TransitionTestAssertionScope {
-            override fun isElement(element: ElementKey, scene: SceneKey?): SemanticsMatcher {
-                return if (scene == null) {
-                    hasTestTag(element.testTag)
-                } else {
-                    hasTestTag(element.testTag) and hasParent(hasTestTag(scene.testTag))
-                }
-            }
-
             override fun onElement(
                 element: ElementKey,
                 scene: SceneKey?
             ): SemanticsNodeInteraction {
                 return onNode(isElement(element, scene))
             }
-
-            override fun onSharedElement(element: ElementKey): SemanticsNodeInteractionCollection {
-                val interaction = onAllNodesWithTag(element.testTag)
-                val matches = interaction.fetchSemanticsNodes(atLeastOneRootRequired = false).size
-                if (matches < 2) {
-                    error("Element $element is not shared ($matches matches)")
-                }
-                return interaction
-            }
         }
 
     var currentScene by mutableStateOf(from)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
new file mode 100644
index 0000000..0b7c3f9
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.shared.notifications.data.repository
+
+import android.provider.Settings
+import com.android.systemui.shared.notifications.shared.model.NotificationSettingsModel
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.withContext
+
+/** Provides access to state related to notifications. */
+class NotificationSettingsRepository(
+    scope: CoroutineScope,
+    private val backgroundDispatcher: CoroutineDispatcher,
+    private val secureSettingsRepository: SecureSettingsRepository,
+) {
+    /** The current state of the notification setting. */
+    val settings: SharedFlow<NotificationSettingsModel> =
+        secureSettingsRepository
+            .intSetting(
+                name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
+            )
+            .map { lockScreenShowNotificationsInt ->
+                NotificationSettingsModel(
+                    isShowNotificationsOnLockScreenEnabled = lockScreenShowNotificationsInt == 1,
+                )
+            }
+            .shareIn(
+                scope = scope,
+                started = SharingStarted.WhileSubscribed(),
+                replay = 1,
+            )
+
+    suspend fun getSettings(): NotificationSettingsModel {
+        return withContext(backgroundDispatcher) {
+            NotificationSettingsModel(
+                isShowNotificationsOnLockScreenEnabled =
+                    secureSettingsRepository.get(
+                        name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
+                        defaultValue = 0,
+                    ) == 1
+            )
+        }
+    }
+
+    suspend fun setSettings(model: NotificationSettingsModel) {
+        withContext(backgroundDispatcher) {
+            secureSettingsRepository.set(
+                name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
+                value = if (model.isShowNotificationsOnLockScreenEnabled) 1 else 0,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt
new file mode 100644
index 0000000..21f3aca
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.shared.notifications.domain.interactor
+
+import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
+import com.android.systemui.shared.notifications.shared.model.NotificationSettingsModel
+import kotlinx.coroutines.flow.Flow
+
+/** Encapsulates business logic for interacting with notification settings. */
+class NotificationSettingsInteractor(
+    private val repository: NotificationSettingsRepository,
+) {
+    /** The current state of the notification setting. */
+    val settings: Flow<NotificationSettingsModel> = repository.settings
+
+    /** Toggles the setting to show or hide notifications on the lock screen. */
+    suspend fun toggleShowNotificationsOnLockScreenEnabled() {
+        val currentModel = repository.getSettings()
+        setSettings(
+            currentModel.copy(
+                isShowNotificationsOnLockScreenEnabled =
+                    !currentModel.isShowNotificationsOnLockScreenEnabled,
+            )
+        )
+    }
+
+    suspend fun setSettings(model: NotificationSettingsModel) {
+        repository.setSettings(model)
+    }
+
+    suspend fun getSettings(): NotificationSettingsModel {
+        return repository.getSettings()
+    }
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/shared/model/NotificationSettingsModel.kt
similarity index 68%
copy from core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl
copy to packages/SystemUI/customization/src/com/android/systemui/shared/notifications/shared/model/NotificationSettingsModel.kt
index 6c1f0fc..7e35360 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/shared/model/NotificationSettingsModel.kt
@@ -12,13 +12,13 @@
  * 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.companion.virtual.camera;
+package com.android.systemui.shared.notifications.shared.model
 
-/**
- * Data structure used to store {@link android.hardware.camera2.CameraMetadata} compatible with
- * VirtualCamera.
- * @hide
- */
-parcelable VirtualCameraMetadata;
+/** Models notification settings. */
+data class NotificationSettingsModel(
+    /** Whether notifications are shown on the lock screen. */
+    val isShowNotificationsOnLockScreenEnabled: Boolean = false,
+)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
new file mode 100644
index 0000000..7ef16a8
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
@@ -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 com.android.systemui.shared.settings.data.repository
+
+import android.content.ContentResolver
+import android.database.ContentObserver
+import android.provider.Settings
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.withContext
+
+/** Defines interface for classes that can provide access to data from [Settings.Secure]. */
+interface SecureSettingsRepository {
+
+    /** Returns a [Flow] tracking the value of a setting as an [Int]. */
+    fun intSetting(
+        name: String,
+        defaultValue: Int = 0,
+    ): Flow<Int>
+
+    /** Updates the value of the setting with the given name. */
+    suspend fun set(
+        name: String,
+        value: Int,
+    )
+
+    suspend fun get(
+        name: String,
+        defaultValue: Int = 0,
+    ): Int
+}
+
+class SecureSettingsRepositoryImpl(
+    private val contentResolver: ContentResolver,
+    private val backgroundDispatcher: CoroutineDispatcher,
+) : SecureSettingsRepository {
+
+    override fun intSetting(
+        name: String,
+        defaultValue: Int,
+    ): Flow<Int> {
+        return callbackFlow {
+                val observer =
+                    object : ContentObserver(null) {
+                        override fun onChange(selfChange: Boolean) {
+                            trySend(Unit)
+                        }
+                    }
+
+                contentResolver.registerContentObserver(
+                    Settings.Secure.getUriFor(name),
+                    /* notifyForDescendants= */ false,
+                    observer,
+                )
+                send(Unit)
+
+                awaitClose { contentResolver.unregisterContentObserver(observer) }
+            }
+            .map { Settings.Secure.getInt(contentResolver, name, defaultValue) }
+            // The above work is done on the background thread (which is important for accessing
+            // settings through the content resolver).
+            .flowOn(backgroundDispatcher)
+    }
+
+    override suspend fun set(name: String, value: Int) {
+        withContext(backgroundDispatcher) {
+            Settings.Secure.putInt(
+                contentResolver,
+                name,
+                value,
+            )
+        }
+    }
+
+    override suspend fun get(name: String, defaultValue: Int): Int {
+        return withContext(backgroundDispatcher) {
+            Settings.Secure.getInt(
+                contentResolver,
+                name,
+                defaultValue,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/customization/tests/utils/Android.bp b/packages/SystemUI/customization/tests/utils/Android.bp
new file mode 100644
index 0000000..6db1410
--- /dev/null
+++ b/packages/SystemUI/customization/tests/utils/Android.bp
@@ -0,0 +1,33 @@
+// 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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+java_library {
+    name: "SystemUICustomizationTestUtils",
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+    static_libs: [
+        "SystemUICustomizationLib",
+    ],
+}
diff --git a/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt b/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt
new file mode 100644
index 0000000..1c86a07
--- /dev/null
+++ b/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.shared.settings.data.repository
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.map
+
+class FakeSecureSettingsRepository : SecureSettingsRepository {
+
+    private val settings = MutableStateFlow<Map<String, String>>(mutableMapOf())
+
+    override fun intSetting(name: String, defaultValue: Int): Flow<Int> {
+        return settings.map { it.getOrDefault(name, defaultValue.toString()) }.map { it.toInt() }
+    }
+
+    override suspend fun set(name: String, value: Int) {
+        settings.value = settings.value.toMutableMap().apply { this[name] = value.toString() }
+    }
+
+    override suspend fun get(name: String, defaultValue: Int): Int {
+        return settings.value[name]?.toInt() ?: defaultValue
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
new file mode 100644
index 0000000..3f05fef
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
@@ -0,0 +1,140 @@
+/*
+ * 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.os.UserHandle
+import android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ColorInversionRepositoryImplTest : SysuiTestCase() {
+
+    private val testDispatcher = StandardTestDispatcher()
+    private val scope = TestScope(testDispatcher)
+    private val settings: FakeSettings = FakeSettings()
+
+    private lateinit var underTest: ColorInversionRepository
+
+    @Before
+    fun setUp() {
+        underTest =
+            ColorInversionRepositoryImpl(
+                testDispatcher,
+                settings,
+            )
+    }
+
+    @Test
+    fun isEnabled_initiallyGetsSettingsValue() =
+        scope.runTest {
+            settings.putIntForUser(SETTING_NAME, 1, TEST_USER_1.identifier)
+
+            underTest =
+                ColorInversionRepositoryImpl(
+                    testDispatcher,
+                    settings,
+                )
+
+            underTest.isEnabled(TEST_USER_1).launchIn(backgroundScope)
+            runCurrent()
+
+            val actualValue: Boolean = underTest.isEnabled(TEST_USER_1).first()
+            assertThat(actualValue).isTrue()
+        }
+
+    @Test
+    fun isEnabled_settingUpdated_valueUpdated() =
+        scope.runTest {
+            underTest.isEnabled(TEST_USER_1).launchIn(backgroundScope)
+
+            settings.putIntForUser(SETTING_NAME, DISABLED, TEST_USER_1.identifier)
+            runCurrent()
+            assertThat(underTest.isEnabled(TEST_USER_1).first()).isFalse()
+
+            settings.putIntForUser(SETTING_NAME, ENABLED, TEST_USER_1.identifier)
+            runCurrent()
+            assertThat(underTest.isEnabled(TEST_USER_1).first()).isTrue()
+
+            settings.putIntForUser(SETTING_NAME, DISABLED, TEST_USER_1.identifier)
+            runCurrent()
+            assertThat(underTest.isEnabled(TEST_USER_1).first()).isFalse()
+        }
+
+    @Test
+    fun isEnabled_settingForUserOneOnly_valueUpdatedForUserOneOnly() =
+        scope.runTest {
+            underTest.isEnabled(TEST_USER_1).launchIn(backgroundScope)
+            settings.putIntForUser(SETTING_NAME, DISABLED, TEST_USER_1.identifier)
+            underTest.isEnabled(TEST_USER_2).launchIn(backgroundScope)
+            settings.putIntForUser(SETTING_NAME, DISABLED, TEST_USER_2.identifier)
+
+            runCurrent()
+            assertThat(underTest.isEnabled(TEST_USER_1).first()).isFalse()
+            assertThat(underTest.isEnabled(TEST_USER_2).first()).isFalse()
+
+            settings.putIntForUser(SETTING_NAME, ENABLED, TEST_USER_1.identifier)
+            runCurrent()
+            assertThat(underTest.isEnabled(TEST_USER_1).first()).isTrue()
+            assertThat(underTest.isEnabled(TEST_USER_2).first()).isFalse()
+        }
+
+    @Test
+    fun setEnabled() =
+        scope.runTest {
+            val success = underTest.setIsEnabled(true, TEST_USER_1)
+            runCurrent()
+            assertThat(success).isTrue()
+
+            val actualValue = settings.getIntForUser(SETTING_NAME, TEST_USER_1.identifier)
+            assertThat(actualValue).isEqualTo(ENABLED)
+        }
+
+    @Test
+    fun setDisabled() =
+        scope.runTest {
+            val success = underTest.setIsEnabled(false, TEST_USER_1)
+            runCurrent()
+            assertThat(success).isTrue()
+
+            val actualValue = settings.getIntForUser(SETTING_NAME, TEST_USER_1.identifier)
+            assertThat(actualValue).isEqualTo(DISABLED)
+        }
+
+    companion object {
+        private const val SETTING_NAME = ACCESSIBILITY_DISPLAY_INVERSION_ENABLED
+        private const val DISABLED = 0
+        private const val ENABLED = 1
+        private val TEST_USER_1 = UserHandle.of(1)!!
+        private val TEST_USER_2 = UserHandle.of(2)!!
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
index 0870122..adf4fc6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -32,18 +32,15 @@
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.util.time.SystemClock
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class AlternateBouncerInteractorTest : SysuiTestCase() {
@@ -167,7 +164,6 @@
     }
 
     @Test
-    @Ignore("b/287599719")
     fun canShowAlternateBouncerForFingerprint_rearFps() {
         mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
         initializeUnderTest()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt
new file mode 100644
index 0000000..9daf186
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+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.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.collect.Range
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AlternateBouncerToAodTransitionViewModelTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
+    private val biometricSettingsRepository = kosmos.biometricSettingsRepository
+    private val underTest = kosmos.alternateBouncerToAodTransitionViewModel
+
+    @Test
+    fun deviceEntryParentViewAppear() =
+        testScope.runTest {
+            fingerprintPropertyRepository.setProperties(
+                sensorId = 0,
+                strength = SensorStrength.STRONG,
+                sensorType = FingerprintSensorType.UDFPS_OPTICAL,
+                sensorLocations = emptyMap(),
+            )
+            biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+            val values by collectValues(underTest.deviceEntryParentViewAlpha)
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                listOf(
+                    step(0f, TransitionState.STARTED),
+                    step(0f),
+                    step(0.1f),
+                    step(0.2f),
+                    step(0.3f),
+                    step(1f),
+                ),
+                testScope,
+            )
+
+            values.forEach { assertThat(it).isEqualTo(1f) }
+        }
+
+    @Test
+    fun deviceEntryBackgroundViewDisappear() =
+        testScope.runTest {
+            val values by collectValues(underTest.deviceEntryBackgroundViewAlpha)
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                listOf(
+                    step(0f, TransitionState.STARTED),
+                    step(0f),
+                    step(0.1f),
+                    step(0.2f),
+                    step(0.3f),
+                    step(1f),
+                ),
+                testScope,
+            )
+
+            assertThat(values.size).isEqualTo(6)
+            values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
+        }
+
+    private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep {
+        return TransitionStep(
+            from = KeyguardState.ALTERNATE_BOUNCER,
+            to = AOD,
+            value = value,
+            transitionState = state,
+            ownerName = "AlternateBouncerToAodTransitionViewModelTest"
+        )
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt
new file mode 100644
index 0000000..3f7e0df
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionStep
+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.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AlternateBouncerToGoneTransitionViewModelTest : SysuiTestCase() {
+    val kosmos =
+        testKosmos().apply {
+            fakeFeatureFlagsClassic.apply {
+                set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
+                set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+            }
+        }
+    private val testScope = kosmos.testScope
+    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val underTest = kosmos.alternateBouncerToGoneTransitionViewModel
+
+    @Test
+    fun deviceEntryParentViewDisappear() =
+        testScope.runTest {
+            val values by collectValues(underTest.deviceEntryParentViewAlpha)
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                listOf(
+                    step(0f, TransitionState.STARTED),
+                    step(0f),
+                    step(0.1f),
+                    step(0.2f),
+                    step(0.3f),
+                    step(1f),
+                ),
+                testScope,
+            )
+
+            values.forEach { assertThat(it).isEqualTo(0f) }
+        }
+
+    private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep {
+        return TransitionStep(
+            from = KeyguardState.ALTERNATE_BOUNCER,
+            to = GONE,
+            value = value,
+            transitionState = state,
+            ownerName = "AlternateBouncerToGoneTransitionViewModelTest"
+        )
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
index cf076c5..a84b9fa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
@@ -24,16 +24,19 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectValues
-import com.android.systemui.qs.external.FakeCustomTileStatePersister
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.qs.external.TileServiceKey
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat
 import com.android.systemui.qs.tiles.impl.custom.commons.copy
+import com.android.systemui.qs.tiles.impl.custom.customTileStatePersister
 import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
+import com.android.systemui.qs.tiles.impl.custom.packageManagerAdapterFacade
+import com.android.systemui.qs.tiles.impl.custom.tileSpec
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -44,194 +47,260 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 class CustomTileRepositoryTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-
-    private val persister = FakeCustomTileStatePersister()
-
+    private val kosmos = Kosmos().apply { tileSpec = TileSpec.create(TEST_COMPONENT) }
     private val underTest: CustomTileRepository =
-        CustomTileRepositoryImpl(
-            TileSpec.create(TEST_COMPONENT),
-            persister,
-            testScope.testScheduler,
-        )
+        with(kosmos) {
+            CustomTileRepositoryImpl(
+                tileSpec,
+                customTileStatePersister,
+                packageManagerAdapterFacade.packageManagerAdapter,
+                testScope.testScheduler,
+            )
+        }
 
     @Test
     fun persistableTileIsRestoredForUser() =
-        testScope.runTest {
-            persister.persistState(TEST_TILE_KEY_1, TEST_TILE_1)
-            persister.persistState(TEST_TILE_KEY_2, TEST_TILE_2)
+        with(kosmos) {
+            testScope.runTest {
+                customTileStatePersister.persistState(TEST_TILE_KEY_1, TEST_TILE_1)
+                customTileStatePersister.persistState(TEST_TILE_KEY_2, TEST_TILE_2)
 
-            underTest.restoreForTheUserIfNeeded(TEST_USER_1, true)
-            runCurrent()
+                underTest.restoreForTheUserIfNeeded(TEST_USER_1, true)
+                runCurrent()
 
-            assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
-            assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
+                assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
+                assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
+            }
         }
 
     @Test
     fun notPersistableTileIsNotRestored() =
-        testScope.runTest {
-            persister.persistState(TEST_TILE_KEY_1, TEST_TILE_1)
-            val tiles = collectValues(underTest.getTiles(TEST_USER_1))
+        with(kosmos) {
+            testScope.runTest {
+                customTileStatePersister.persistState(TEST_TILE_KEY_1, TEST_TILE_1)
+                val tiles = collectValues(underTest.getTiles(TEST_USER_1))
 
-            underTest.restoreForTheUserIfNeeded(TEST_USER_1, false)
-            runCurrent()
+                underTest.restoreForTheUserIfNeeded(TEST_USER_1, false)
+                runCurrent()
 
-            assertThat(tiles()).isEmpty()
+                assertThat(tiles()).isEmpty()
+            }
         }
 
     @Test
     fun emptyPersistedStateIsHandled() =
-        testScope.runTest {
-            val tiles = collectValues(underTest.getTiles(TEST_USER_1))
+        with(kosmos) {
+            testScope.runTest {
+                val tiles = collectValues(underTest.getTiles(TEST_USER_1))
 
-            underTest.restoreForTheUserIfNeeded(TEST_USER_1, true)
-            runCurrent()
+                underTest.restoreForTheUserIfNeeded(TEST_USER_1, true)
+                runCurrent()
 
-            assertThat(tiles()).isEmpty()
+                assertThat(tiles()).isEmpty()
+            }
         }
 
     @Test
     fun updatingWithPersistableTilePersists() =
-        testScope.runTest {
-            underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
-            runCurrent()
+        with(kosmos) {
+            testScope.runTest {
+                underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+                runCurrent()
 
-            assertThat(persister.readState(TEST_TILE_KEY_1)).isEqualTo(TEST_TILE_1)
+                assertThat(customTileStatePersister.readState(TEST_TILE_KEY_1))
+                    .isEqualTo(TEST_TILE_1)
+            }
         }
 
     @Test
     fun updatingWithNotPersistableTileDoesntPersist() =
-        testScope.runTest {
-            underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, false)
-            runCurrent()
+        with(kosmos) {
+            testScope.runTest {
+                underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, false)
+                runCurrent()
 
-            assertThat(persister.readState(TEST_TILE_KEY_1)).isNull()
+                assertThat(customTileStatePersister.readState(TEST_TILE_KEY_1)).isNull()
+            }
         }
 
     @Test
     fun updateWithTileEmits() =
-        testScope.runTest {
-            underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
-            runCurrent()
+        with(kosmos) {
+            testScope.runTest {
+                underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+                runCurrent()
 
-            assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
-            assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
+                assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
+                assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
+            }
         }
 
     @Test
     fun updatingPeristableWithDefaultsPersists() =
-        testScope.runTest {
-            underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
-            runCurrent()
+        with(kosmos) {
+            testScope.runTest {
+                underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
+                runCurrent()
 
-            assertThat(persister.readState(TEST_TILE_KEY_1)).isEqualTo(TEST_TILE_1)
+                assertThat(customTileStatePersister.readState(TEST_TILE_KEY_1))
+                    .isEqualTo(TEST_TILE_1)
+            }
         }
 
     @Test
     fun updatingNotPersistableWithDefaultsDoesntPersist() =
-        testScope.runTest {
-            underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, false)
-            runCurrent()
+        with(kosmos) {
+            testScope.runTest {
+                underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, false)
+                runCurrent()
 
-            assertThat(persister.readState(TEST_TILE_KEY_1)).isNull()
+                assertThat(customTileStatePersister.readState(TEST_TILE_KEY_1)).isNull()
+            }
         }
 
     @Test
     fun updatingPeristableWithErrorDefaultsDoesntPersist() =
-        testScope.runTest {
-            underTest.updateWithDefaults(TEST_USER_1, CustomTileDefaults.Error, true)
-            runCurrent()
+        with(kosmos) {
+            testScope.runTest {
+                underTest.updateWithDefaults(TEST_USER_1, CustomTileDefaults.Error, true)
+                runCurrent()
 
-            assertThat(persister.readState(TEST_TILE_KEY_1)).isNull()
+                assertThat(customTileStatePersister.readState(TEST_TILE_KEY_1)).isNull()
+            }
         }
 
     @Test
     fun updateWithDefaultsEmits() =
-        testScope.runTest {
-            underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
-            runCurrent()
+        with(kosmos) {
+            testScope.runTest {
+                underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
+                runCurrent()
 
-            assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
-            assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
+                assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
+                assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
+            }
         }
 
     @Test
     fun getTileForAnotherUserReturnsNull() =
-        testScope.runTest {
-            underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
-            runCurrent()
+        with(kosmos) {
+            testScope.runTest {
+                underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+                runCurrent()
 
-            assertThat(underTest.getTile(TEST_USER_2)).isNull()
+                assertThat(underTest.getTile(TEST_USER_2)).isNull()
+            }
         }
 
     @Test
     fun getTilesForAnotherUserEmpty() =
-        testScope.runTest {
-            val tiles = collectValues(underTest.getTiles(TEST_USER_2))
+        with(kosmos) {
+            testScope.runTest {
+                val tiles = collectValues(underTest.getTiles(TEST_USER_2))
 
-            underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
-            runCurrent()
+                underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+                runCurrent()
 
-            assertThat(tiles()).isEmpty()
+                assertThat(tiles()).isEmpty()
+            }
         }
 
     @Test
     fun updatingWithTileForTheSameUserAddsData() =
-        testScope.runTest {
-            underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
-            runCurrent()
+        with(kosmos) {
+            testScope.runTest {
+                underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+                runCurrent()
 
-            underTest.updateWithTile(TEST_USER_1, Tile().apply { subtitle = "test_subtitle" }, true)
-            runCurrent()
+                underTest.updateWithTile(
+                    TEST_USER_1,
+                    Tile().apply { subtitle = "test_subtitle" },
+                    true
+                )
+                runCurrent()
 
-            val expectedTile = TEST_TILE_1.copy().apply { subtitle = "test_subtitle" }
-            assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(expectedTile)
-            assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(expectedTile)
+                val expectedTile = TEST_TILE_1.copy().apply { subtitle = "test_subtitle" }
+                assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(expectedTile)
+                assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(expectedTile)
+            }
         }
 
     @Test
     fun updatingWithTileForAnotherUserOverridesTile() =
-        testScope.runTest {
-            underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
-            runCurrent()
+        with(kosmos) {
+            testScope.runTest {
+                underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+                runCurrent()
 
-            val tiles = collectValues(underTest.getTiles(TEST_USER_2))
-            underTest.updateWithTile(TEST_USER_2, TEST_TILE_2, true)
-            runCurrent()
+                val tiles = collectValues(underTest.getTiles(TEST_USER_2))
+                underTest.updateWithTile(TEST_USER_2, TEST_TILE_2, true)
+                runCurrent()
 
-            assertThat(underTest.getTile(TEST_USER_2)).isEqualTo(TEST_TILE_2)
-            assertThat(tiles()).hasSize(1)
-            assertThat(tiles().last()).isEqualTo(TEST_TILE_2)
+                assertThat(underTest.getTile(TEST_USER_2)).isEqualTo(TEST_TILE_2)
+                assertThat(tiles()).hasSize(1)
+                assertThat(tiles().last()).isEqualTo(TEST_TILE_2)
+            }
         }
 
     @Test
     fun updatingWithDefaultsForTheSameUserAddsData() =
-        testScope.runTest {
-            underTest.updateWithTile(TEST_USER_1, Tile().apply { subtitle = "test_subtitle" }, true)
-            runCurrent()
+        with(kosmos) {
+            testScope.runTest {
+                underTest.updateWithTile(
+                    TEST_USER_1,
+                    Tile().apply { subtitle = "test_subtitle" },
+                    true
+                )
+                runCurrent()
 
-            underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
-            runCurrent()
+                underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
+                runCurrent()
 
-            val expectedTile = TEST_TILE_1.copy().apply { subtitle = "test_subtitle" }
-            assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(expectedTile)
-            assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(expectedTile)
+                val expectedTile = TEST_TILE_1.copy().apply { subtitle = "test_subtitle" }
+                assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(expectedTile)
+                assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(expectedTile)
+            }
         }
 
     @Test
     fun updatingWithDefaultsForAnotherUserOverridesTile() =
-        testScope.runTest {
-            underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
-            runCurrent()
+        with(kosmos) {
+            testScope.runTest {
+                underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
+                runCurrent()
 
-            val tiles = collectValues(underTest.getTiles(TEST_USER_2))
-            underTest.updateWithDefaults(TEST_USER_2, TEST_DEFAULTS_2, true)
-            runCurrent()
+                val tiles = collectValues(underTest.getTiles(TEST_USER_2))
+                underTest.updateWithDefaults(TEST_USER_2, TEST_DEFAULTS_2, true)
+                runCurrent()
 
-            assertThat(underTest.getTile(TEST_USER_2)).isEqualTo(TEST_TILE_2)
-            assertThat(tiles()).hasSize(1)
-            assertThat(tiles().last()).isEqualTo(TEST_TILE_2)
+                assertThat(underTest.getTile(TEST_USER_2)).isEqualTo(TEST_TILE_2)
+                assertThat(tiles()).hasSize(1)
+                assertThat(tiles().last()).isEqualTo(TEST_TILE_2)
+            }
+        }
+
+    @Test
+    fun isActiveFollowsPackageManagerAdapter() =
+        with(kosmos) {
+            testScope.runTest {
+                packageManagerAdapterFacade.setIsActive(false)
+                assertThat(underTest.isTileActive()).isFalse()
+
+                packageManagerAdapterFacade.setIsActive(true)
+                assertThat(underTest.isTileActive()).isTrue()
+            }
+        }
+
+    @Test
+    fun isToggleableFollowsPackageManagerAdapter() =
+        with(kosmos) {
+            testScope.runTest {
+                packageManagerAdapterFacade.setIsToggleable(false)
+                assertThat(underTest.isTileToggleable()).isFalse()
+
+                packageManagerAdapterFacade.setIsToggleable(true)
+                assertThat(underTest.isTileToggleable()).isTrue()
+            }
         }
 
     private companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
index eebb145..90779cb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
@@ -25,157 +25,159 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectValues
-import com.android.systemui.qs.external.FakeCustomTileStatePersister
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.qs.external.TileServiceKey
-import com.android.systemui.qs.external.TileServiceManager
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat
+import com.android.systemui.qs.tiles.impl.custom.customTileDefaultsRepository
+import com.android.systemui.qs.tiles.impl.custom.customTileRepository
+import com.android.systemui.qs.tiles.impl.custom.customTileStatePersister
 import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
-import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileDefaultsRepository
-import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileRepository
-import com.android.systemui.util.mockito.whenever
+import com.android.systemui.qs.tiles.impl.custom.tileSpec
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.advanceTimeBy
 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.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @OptIn(ExperimentalCoroutinesApi::class)
 class CustomTileInteractorTest : SysuiTestCase() {
 
-    @Mock private lateinit var tileServiceManager: TileServiceManager
+    private val kosmos = Kosmos().apply { tileSpec = TileSpec.create(TEST_COMPONENT) }
 
-    private val testScope = TestScope()
-
-    private val defaultsRepository = FakeCustomTileDefaultsRepository()
-    private val customTileStatePersister = FakeCustomTileStatePersister()
-    private val customTileRepository =
-        FakeCustomTileRepository(
-            TEST_TILE_SPEC,
-            customTileStatePersister,
-            testScope.testScheduler,
-        )
-
-    private lateinit var underTest: CustomTileInteractor
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-
-        underTest =
+    private val underTest: CustomTileInteractor =
+        with(kosmos) {
             CustomTileInteractor(
-                TEST_USER,
-                defaultsRepository,
+                customTileDefaultsRepository,
                 customTileRepository,
-                tileServiceManager,
                 testScope.backgroundScope,
                 testScope.testScheduler,
             )
-    }
+        }
 
     @Test
     fun activeTileIsAvailableAfterRestored() =
-        testScope.runTest {
-            whenever(tileServiceManager.isActiveTile).thenReturn(true)
-            customTileStatePersister.persistState(
-                TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
-                TEST_TILE,
-            )
+        with(kosmos) {
+            testScope.runTest {
+                customTileRepository.setTileActive(true)
+                customTileStatePersister.persistState(
+                    TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
+                    TEST_TILE,
+                )
 
-            underTest.init()
+                underTest.initForUser(TEST_USER)
 
-            assertThat(underTest.tile).isEqualTo(TEST_TILE)
-            assertThat(underTest.tiles.first()).isEqualTo(TEST_TILE)
+                assertThat(underTest.getTile(TEST_USER)).isEqualTo(TEST_TILE)
+                assertThat(underTest.getTiles(TEST_USER).first()).isEqualTo(TEST_TILE)
+            }
         }
 
     @Test
     fun notActiveTileIsAvailableAfterUpdated() =
-        testScope.runTest {
-            whenever(tileServiceManager.isActiveTile).thenReturn(false)
-            customTileStatePersister.persistState(
-                TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
-                TEST_TILE,
-            )
-            val tiles = collectValues(underTest.tiles)
-            val initJob = launch { underTest.init() }
+        with(kosmos) {
+            testScope.runTest {
+                customTileRepository.setTileActive(false)
+                customTileStatePersister.persistState(
+                    TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
+                    TEST_TILE,
+                )
+                val tiles = collectValues(underTest.getTiles(TEST_USER))
+                val initJob = launch { underTest.initForUser(TEST_USER) }
 
-            underTest.updateTile(TEST_TILE)
-            runCurrent()
-            initJob.join()
+                underTest.updateTile(TEST_TILE)
+                runCurrent()
+                initJob.join()
 
-            assertThat(tiles()).hasSize(1)
-            assertThat(tiles().last()).isEqualTo(TEST_TILE)
+                assertThat(tiles()).hasSize(1)
+                assertThat(tiles().last()).isEqualTo(TEST_TILE)
+            }
         }
 
     @Test
     fun notActiveTileIsAvailableAfterDefaultsUpdated() =
-        testScope.runTest {
-            whenever(tileServiceManager.isActiveTile).thenReturn(false)
-            customTileStatePersister.persistState(
-                TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
-                TEST_TILE,
-            )
-            val tiles = collectValues(underTest.tiles)
-            val initJob = launch { underTest.init() }
+        with(kosmos) {
+            testScope.runTest {
+                customTileRepository.setTileActive(false)
+                customTileStatePersister.persistState(
+                    TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
+                    TEST_TILE,
+                )
+                val tiles = collectValues(underTest.getTiles(TEST_USER))
+                val initJob = launch { underTest.initForUser(TEST_USER) }
 
-            defaultsRepository.putDefaults(TEST_USER, TEST_COMPONENT, TEST_DEFAULTS)
-            defaultsRepository.requestNewDefaults(TEST_USER, TEST_COMPONENT)
-            runCurrent()
-            initJob.join()
+                customTileDefaultsRepository.putDefaults(TEST_USER, TEST_COMPONENT, TEST_DEFAULTS)
+                customTileDefaultsRepository.requestNewDefaults(TEST_USER, TEST_COMPONENT)
+                runCurrent()
+                initJob.join()
 
-            assertThat(tiles()).hasSize(1)
-            assertThat(tiles().last()).isEqualTo(TEST_TILE)
+                assertThat(tiles()).hasSize(1)
+                assertThat(tiles().last()).isEqualTo(TEST_TILE)
+            }
         }
 
     @Test(expected = IllegalStateException::class)
-    fun getTileBeforeInitThrows() = testScope.runTest { underTest.tile }
+    fun getTileBeforeInitThrows() =
+        with(kosmos) { testScope.runTest { underTest.getTile(TEST_USER) } }
 
     @Test
     fun initSuspendsForActiveTileNotRestoredAndNotUpdated() =
-        testScope.runTest {
-            whenever(tileServiceManager.isActiveTile).thenReturn(true)
-            val tiles = collectValues(underTest.tiles)
+        with(kosmos) {
+            testScope.runTest {
+                customTileRepository.setTileActive(true)
+                val tiles = collectValues(underTest.getTiles(TEST_USER))
 
-            val initJob = backgroundScope.launch { underTest.init() }
-            advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS)
+                val initJob = backgroundScope.launch { underTest.initForUser(TEST_USER) }
+                advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS)
 
-            // Is still suspended
-            assertThat(initJob.isActive).isTrue()
-            assertThat(tiles()).isEmpty()
+                // Is still suspended
+                assertThat(initJob.isActive).isTrue()
+                assertThat(tiles()).isEmpty()
+            }
         }
 
     @Test
     fun initSuspendedForNotActiveTileWithoutUpdates() =
-        testScope.runTest {
-            whenever(tileServiceManager.isActiveTile).thenReturn(false)
-            customTileStatePersister.persistState(
-                TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
-                TEST_TILE,
-            )
-            val tiles = collectValues(underTest.tiles)
+        with(kosmos) {
+            testScope.runTest {
+                customTileRepository.setTileActive(false)
+                customTileStatePersister.persistState(
+                    TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
+                    TEST_TILE,
+                )
+                val tiles = collectValues(underTest.getTiles(TEST_USER))
 
-            val initJob = backgroundScope.launch { underTest.init() }
-            advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS)
+                val initJob = backgroundScope.launch { underTest.initForUser(TEST_USER) }
+                advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS)
 
-            // Is still suspended
-            assertThat(initJob.isActive).isTrue()
-            assertThat(tiles()).isEmpty()
+                // Is still suspended
+                assertThat(initJob.isActive).isTrue()
+                assertThat(tiles()).isEmpty()
+            }
         }
 
+    @Test
+    fun toggleableFollowsTheRepository() {
+        with(kosmos) {
+            testScope.runTest {
+                customTileRepository.setTileToggleable(false)
+                assertThat(underTest.isTileToggleable()).isFalse()
+
+                customTileRepository.setTileToggleable(true)
+                assertThat(underTest.isTileToggleable()).isTrue()
+            }
+        }
+    }
+
     private companion object {
 
         val TEST_COMPONENT = ComponentName("test.pkg", "test.cls")
-        val TEST_TILE_SPEC = TileSpec.create(TEST_COMPONENT)
         val TEST_USER = UserHandle.of(1)!!
         val TEST_TILE =
             Tile().apply {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt
new file mode 100644
index 0000000..f3c3579
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.tiles.impl.inversion.domain
+
+import android.graphics.drawable.TestStubDrawable
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tileimpl.SubtitleArrayMapping
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.inversion.domain.model.ColorInversionTileModel
+import com.android.systemui.qs.tiles.impl.inversion.qsColorInversionTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ColorInversionTileMapperTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val colorInversionTileConfig = kosmos.qsColorInversionTileConfig
+    private val subtitleArrayId =
+        SubtitleArrayMapping.getSubtitleId(colorInversionTileConfig.tileSpec.spec)
+    private val subtitleArray = context.resources.getStringArray(subtitleArrayId)
+    // Using lazy (versus =) to make sure we override the right context -- see b/311612168
+    private val mapper by lazy {
+        ColorInversionTileMapper(
+            context.orCreateTestableResources
+                .apply {
+                    addOverride(R.drawable.qs_invert_colors_icon_off, TestStubDrawable())
+                    addOverride(R.drawable.qs_invert_colors_icon_on, TestStubDrawable())
+                }
+                .resources,
+            context.theme
+        )
+    }
+
+    @Test
+    fun disabledModel() {
+        val inputModel = ColorInversionTileModel(false)
+
+        val outputState = mapper.map(colorInversionTileConfig, inputModel)
+
+        val expectedState =
+            createColorInversionTileState(
+                QSTileState.ActivationState.INACTIVE,
+                subtitleArray[1],
+                R.drawable.qs_invert_colors_icon_off
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun enabledModel() {
+        val inputModel = ColorInversionTileModel(true)
+
+        val outputState = mapper.map(colorInversionTileConfig, inputModel)
+
+        val expectedState =
+            createColorInversionTileState(
+                QSTileState.ActivationState.ACTIVE,
+                subtitleArray[2],
+                R.drawable.qs_invert_colors_icon_on
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    private fun createColorInversionTileState(
+        activationState: QSTileState.ActivationState,
+        secondaryLabel: String,
+        iconRes: Int,
+    ): QSTileState {
+        val label = context.getString(R.string.quick_settings_inversion_label)
+        return QSTileState(
+            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            label,
+            activationState,
+            secondaryLabel,
+            setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+            label,
+            null,
+            QSTileState.SideViewIcon.None,
+            QSTileState.EnabledState.ENABLED,
+            Switch::class.qualifiedName
+        )
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionTileDataInteractorTest.kt
new file mode 100644
index 0000000..24c7bfb
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionTileDataInteractorTest.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.inversion.domain.interactor
+
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.data.repository.FakeColorInversionRepository
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.inversion.domain.model.ColorInversionTileModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toCollection
+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 ColorInversionTileDataInteractorTest : SysuiTestCase() {
+
+    private val colorInversionRepository = FakeColorInversionRepository()
+    private val underTest: ColorInversionTileDataInteractor =
+        ColorInversionTileDataInteractor(colorInversionRepository)
+
+    @Test
+    fun alwaysAvailable() = runTest {
+        val availability = underTest.availability(TEST_USER).toCollection(mutableListOf())
+
+        assertThat(availability).hasSize(1)
+        assertThat(availability.last()).isTrue()
+    }
+
+    @Test
+    fun dataMatchesTheRepository() = runTest {
+        val dataList: List<ColorInversionTileModel> by
+            collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)))
+        runCurrent()
+
+        colorInversionRepository.setIsEnabled(true, TEST_USER)
+        runCurrent()
+
+        colorInversionRepository.setIsEnabled(false, TEST_USER)
+        runCurrent()
+
+        assertThat(dataList).hasSize(3)
+        assertThat(dataList.map { it.isEnabled }).isEqualTo(listOf(false, true, false))
+    }
+
+    private companion object {
+        val TEST_USER = UserHandle.of(1)!!
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionUserActionInteractorTest.kt
new file mode 100644
index 0000000..99bae18
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionUserActionInteractorTest.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.tiles.impl.inversion.domain.interactor
+
+import android.os.UserHandle
+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.accessibility.data.repository.FakeColorInversionRepository
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.inversion.domain.model.ColorInversionTileModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ColorInversionUserActionInteractorTest : SysuiTestCase() {
+
+    private val testUser = UserHandle.CURRENT
+    private val repository = FakeColorInversionRepository()
+    private val inputHandler = FakeQSTileIntentUserInputHandler()
+
+    private val underTest =
+        ColorInversionUserActionInteractor(
+            repository,
+            inputHandler,
+        )
+
+    @Test
+    fun handleClickWhenEnabled() = runTest {
+        val wasEnabled = true
+        repository.setIsEnabled(wasEnabled, testUser)
+
+        underTest.handleInput(QSTileInputTestKtx.click(ColorInversionTileModel(wasEnabled)))
+
+        assertThat(repository.isEnabled(testUser).value).isEqualTo(!wasEnabled)
+    }
+
+    @Test
+    fun handleClickWhenDisabled() = runTest {
+        val wasEnabled = false
+        repository.setIsEnabled(wasEnabled, testUser)
+
+        underTest.handleInput(QSTileInputTestKtx.click(ColorInversionTileModel(wasEnabled)))
+
+        assertThat(repository.isEnabled(testUser).value).isEqualTo(!wasEnabled)
+    }
+
+    @Test
+    fun handleLongClickWhenDisabled() = runTest {
+        val enabled = false
+
+        underTest.handleInput(QSTileInputTestKtx.longClick(ColorInversionTileModel(enabled)))
+
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+            assertThat(it.intent.action).isEqualTo(Settings.ACTION_COLOR_INVERSION_SETTINGS)
+        }
+    }
+
+    @Test
+    fun handleLongClickWhenEnabled() = runTest {
+        val enabled = true
+
+        underTest.handleInput(QSTileInputTestKtx.longClick(ColorInversionTileModel(enabled)))
+
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+            assertThat(it.intent.action).isEqualTo(Settings.ACTION_COLOR_INVERSION_SETTINGS)
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenAndDreamTargetFilterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenAndDreamTargetFilterTest.kt
deleted file mode 100644
index 886c61a..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenAndDreamTargetFilterTest.kt
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.smartspace
-
-import android.app.smartspace.SmartspaceTarget
-import android.content.ContentResolver
-import android.database.ContentObserver
-import android.net.Uri
-import android.os.Handler
-import android.os.UserHandle
-import android.provider.Settings
-import android.testing.TestableLooper
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.smartspace.filters.LockscreenAndDreamTargetFilter
-import com.android.systemui.util.concurrency.Execution
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.withArgCaptor
-import com.android.systemui.util.settings.SecureSettings
-import com.google.common.truth.Truth.assertThat
-import java.util.concurrent.Executor
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.Mockito.atLeast
-import org.mockito.Mockito.clearInvocations
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@TestableLooper.RunWithLooper
-@RunWith(AndroidJUnit4::class)
-class LockscreenAndDreamTargetFilterTest : SysuiTestCase() {
-    @Mock
-    private lateinit var secureSettings: SecureSettings
-
-    @Mock
-    private lateinit var userTracker: UserTracker
-
-    @Mock
-    private lateinit var execution: Execution
-
-    @Mock
-    private lateinit var handler: Handler
-
-    @Mock
-    private lateinit var contentResolver: ContentResolver
-
-    @Mock
-    private lateinit var uiExecution: Executor
-
-    @Mock
-    private lateinit var userHandle: UserHandle
-
-    @Mock
-    private lateinit var listener: SmartspaceTargetFilter.Listener
-
-    @Mock
-    private lateinit var lockScreenAllowPrivateNotificationsUri: Uri
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        `when`(userTracker.userHandle).thenReturn(userHandle)
-        `when`(secureSettings
-                .getUriFor(eq(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS)))
-                .thenReturn(lockScreenAllowPrivateNotificationsUri)
-    }
-
-    /**
-     * Ensures {@link Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS} is
-     * tracked.
-     */
-    @Test
-    fun testLockscreenAllowPrivateNotifications() {
-        var setting = Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS
-        `when`(secureSettings
-                .getIntForUser(eq(setting) ?: setting, anyInt(), anyInt()))
-                .thenReturn(0)
-        var filter = LockscreenAndDreamTargetFilter(secureSettings, userTracker, execution, handler,
-                contentResolver, uiExecution)
-
-        filter.addListener(listener)
-        var smartspaceTarget = mock(SmartspaceTarget::class.java)
-        `when`(smartspaceTarget.userHandle).thenReturn(userHandle)
-        `when`(smartspaceTarget.isSensitive).thenReturn(true)
-        assertThat(filter.filterSmartspaceTarget(smartspaceTarget)).isFalse()
-
-        var settingCaptor = ArgumentCaptor.forClass(ContentObserver::class.java)
-
-        verify(contentResolver).registerContentObserver(eq(lockScreenAllowPrivateNotificationsUri),
-                anyBoolean(), settingCaptor.capture(), anyInt())
-
-        `when`(secureSettings
-                .getIntForUser(eq(setting) ?: setting, anyInt(), anyInt()))
-                .thenReturn(1)
-
-        clearInvocations(listener)
-        settingCaptor.value.onChange(false, mock(Uri::class.java))
-        verify(listener, atLeast(1)).onCriteriaChanged()
-        assertThat(filter.filterSmartspaceTarget(smartspaceTarget)).isTrue()
-    }
-
-    /**
-     * Ensures user switches are tracked.
-     */
-    @Test
-    fun testUserSwitchCallback() {
-        var setting = Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS
-        `when`(secureSettings
-                .getIntForUser(eq(setting) ?: setting, anyInt(), anyInt()))
-                .thenReturn(0)
-        var filter = LockscreenAndDreamTargetFilter(secureSettings, userTracker, execution, handler,
-                contentResolver, uiExecution)
-
-        filter.addListener(listener)
-
-        var userTrackerCallback = withArgCaptor<UserTracker.Callback> {
-            verify(userTracker).addCallback(capture(), any())
-        }
-
-        clearInvocations(listener)
-        userTrackerCallback.onUserChanged(0, context)
-
-        verify(listener).onCriteriaChanged()
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenTargetFilterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenTargetFilterTest.kt
new file mode 100644
index 0000000..bf33010
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenTargetFilterTest.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.smartspace
+
+import android.app.smartspace.SmartspaceTarget
+import android.content.ContentResolver
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Handler
+import android.os.UserHandle
+import android.provider.Settings
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.smartspace.filters.LockscreenTargetFilter
+import com.android.systemui.util.concurrency.Execution
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.settings.SecureSettings
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.atLeast
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidJUnit4::class)
+class LockscreenTargetFilterTest : SysuiTestCase() {
+    @Mock private lateinit var secureSettings: SecureSettings
+
+    @Mock private lateinit var userTracker: UserTracker
+
+    @Mock private lateinit var execution: Execution
+
+    @Mock private lateinit var handler: Handler
+
+    @Mock private lateinit var contentResolver: ContentResolver
+
+    @Mock private lateinit var uiExecution: Executor
+
+    @Mock private lateinit var userHandle: UserHandle
+
+    @Mock private lateinit var listener: SmartspaceTargetFilter.Listener
+
+    @Mock private lateinit var lockScreenAllowPrivateNotificationsUri: Uri
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        `when`(userTracker.userHandle).thenReturn(userHandle)
+        `when`(
+                secureSettings.getUriFor(
+                    eq(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS)
+                )
+            )
+            .thenReturn(lockScreenAllowPrivateNotificationsUri)
+    }
+
+    /** Ensures {@link Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS} is tracked. */
+    @Test
+    fun testLockscreenAllowPrivateNotifications() {
+        var setting = Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS
+        `when`(secureSettings.getIntForUser(eq(setting) ?: setting, anyInt(), anyInt()))
+            .thenReturn(0)
+        var filter =
+            LockscreenTargetFilter(
+                secureSettings,
+                userTracker,
+                execution,
+                handler,
+                contentResolver,
+                uiExecution
+            )
+
+        filter.addListener(listener)
+        var smartspaceTarget = mock(SmartspaceTarget::class.java)
+        `when`(smartspaceTarget.userHandle).thenReturn(userHandle)
+        `when`(smartspaceTarget.isSensitive).thenReturn(true)
+        assertThat(filter.filterSmartspaceTarget(smartspaceTarget)).isFalse()
+
+        var settingCaptor = ArgumentCaptor.forClass(ContentObserver::class.java)
+
+        verify(contentResolver)
+            .registerContentObserver(
+                eq(lockScreenAllowPrivateNotificationsUri),
+                anyBoolean(),
+                settingCaptor.capture(),
+                anyInt()
+            )
+
+        `when`(secureSettings.getIntForUser(eq(setting) ?: setting, anyInt(), anyInt()))
+            .thenReturn(1)
+
+        clearInvocations(listener)
+        settingCaptor.value.onChange(false, mock(Uri::class.java))
+        verify(listener, atLeast(1)).onCriteriaChanged()
+        assertThat(filter.filterSmartspaceTarget(smartspaceTarget)).isTrue()
+    }
+
+    /** Ensures user switches are tracked. */
+    @Test
+    fun testUserSwitchCallback() {
+        var setting = Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS
+        `when`(secureSettings.getIntForUser(eq(setting) ?: setting, anyInt(), anyInt()))
+            .thenReturn(0)
+        var filter =
+            LockscreenTargetFilter(
+                secureSettings,
+                userTracker,
+                execution,
+                handler,
+                contentResolver,
+                uiExecution
+            )
+
+        filter.addListener(listener)
+
+        var userTrackerCallback =
+            withArgCaptor<UserTracker.Callback> {
+                verify(userTracker).addCallback(capture(), any())
+            }
+
+        clearInvocations(listener)
+        userTrackerCallback.onUserChanged(0, context)
+
+        verify(listener).onCriteriaChanged()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationContentDescriptionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationContentDescriptionTest.kt
new file mode 100644
index 0000000..f67c70c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationContentDescriptionTest.kt
@@ -0,0 +1,97 @@
+/*
+ * 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
+
+import android.app.Notification
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NotificationContentDescriptionTest : SysuiTestCase() {
+
+    private val TITLE = "this is a title"
+    private val TEXT = "this is text"
+    private val TICKER = "this is a ticker"
+
+    @Test
+    fun notificationWithAllDifferentFields_descriptionIsTitle() {
+        val n = createNotification(TITLE, TEXT, TICKER)
+        val description = contentDescForNotification(context, n)
+        assertThat(description).isEqualTo(createDescriptionText(n, TITLE))
+    }
+
+    @Test
+    fun notificationWithAllDifferentFields_titleMatchesAppName_descriptionIsText() {
+        val n = createNotification(getTestAppName(), TEXT, TICKER)
+        val description = contentDescForNotification(context, n)
+        assertThat(description).isEqualTo(createDescriptionText(n, TEXT))
+    }
+
+    @Test
+    fun notificationWithAllDifferentFields_titleMatchesAppNameNoText_descriptionIsTicker() {
+        val n = createNotification(getTestAppName(), null, TICKER)
+        val description = contentDescForNotification(context, n)
+        assertThat(description).isEqualTo(createDescriptionText(n, TICKER))
+    }
+
+    @Test
+    fun notificationWithAllDifferentFields_titleMatchesAppNameNoTextNoTicker_descriptionEmpty() {
+        val appName = getTestAppName()
+        val n = createNotification(appName, null, null)
+        val description = contentDescForNotification(context, n)
+        assertThat(description).isEqualTo(createDescriptionText(n, ""))
+    }
+
+    @Test
+    fun nullNotification_descriptionIsAppName() {
+        val description = contentDescForNotification(context, null)
+        assertThat(description).isEqualTo(createDescriptionText(null, ""))
+    }
+
+    private fun createNotification(
+        title: String? = null,
+        text: String? = null,
+        ticker: String? = null
+    ): Notification =
+        Notification.Builder(context)
+            .setContentTitle(title)
+            .setContentText(text)
+            .setTicker(ticker)
+            .build()
+
+    private fun getTestAppName(): String {
+        return getAppName(createNotification("", "", ""))
+    }
+
+    private fun getAppName(n: Notification?) =
+        n?.let {
+            val builder = Notification.Builder.recoverBuilder(context, it)
+            builder.loadHeaderAppName()
+        }
+            ?: ""
+
+    private fun createDescriptionText(n: Notification?, desc: String?): String {
+        val appName = getAppName(n)
+        return context.getString(R.string.accessibility_desc_notification_icon, appName, desc)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index 791a028..1237347 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -16,9 +16,12 @@
 
 package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
 
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.settingslib.AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION
+import com.android.systemui.Flags.FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
 import com.android.systemui.coroutines.collectLastValue
@@ -183,7 +186,8 @@
         }
 
     @Test
-    fun activity_nullSsid_outputsFalse() =
+    @DisableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
+    fun activity_nullSsid_outputsFalse_staticFlagOff() =
         testScope.runTest {
             whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
             createAndSetViewModel()
@@ -207,6 +211,33 @@
         }
 
     @Test
+    @EnableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
+    fun activity_nullSsid_outputsFalse_staticFlagOn() =
+        testScope.runTest {
+            whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
+            createAndSetViewModel()
+
+            wifiRepository.setWifiNetwork(
+                WifiNetworkModel.Active(NETWORK_ID, ssid = null, level = 1)
+            )
+
+            val activityIn by collectLastValue(underTest.isActivityInViewVisible)
+            val activityOut by collectLastValue(underTest.isActivityOutViewVisible)
+            val activityContainer by collectLastValue(underTest.isActivityContainerVisible)
+
+            // WHEN we update the repo to have activity
+            val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = true)
+            wifiRepository.setWifiActivity(activity)
+
+            // THEN we still output false because our network's SSID is null
+            assertThat(activityIn).isFalse()
+            assertThat(activityOut).isFalse()
+
+            // THEN the inout indicators are sill showing due to the config being true
+            assertThat(activityContainer).isTrue()
+        }
+
+    @Test
     fun activity_allLocationViewModelsReceiveSameData() =
         testScope.runTest {
             whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
@@ -335,7 +366,8 @@
         }
 
     @Test
-    fun activityContainer_inAndOutFalse_outputsFalse() =
+    @DisableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
+    fun activityContainer_inAndOutFalse_outputsTrue_staticFlagOff() =
         testScope.runTest {
             whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
             createAndSetViewModel()
@@ -350,6 +382,24 @@
         }
 
     @Test
+    @EnableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
+    fun activityContainer_inAndOutFalse_outputsTrue_staticFlagOn() =
+        testScope.runTest {
+            whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
+            createAndSetViewModel()
+            wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+
+            val latest by collectLastValue(underTest.isActivityContainerVisible)
+
+            val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+            wifiRepository.setWifiActivity(activity)
+
+            // The activity container should always be visible, since activity is
+            // shown in UI by changing opacity of the indicators.
+            assertThat(latest).isTrue()
+        }
+
+    @Test
     fun airplaneSpacer_notAirplaneMode_outputsFalse() =
         testScope.runTest {
             val latest by collectLastValue(underTest.isAirplaneSpacerVisible)
diff --git a/packages/SystemUI/proguard_common.flags b/packages/SystemUI/proguard_common.flags
index 21b019e..f9546c4 100644
--- a/packages/SystemUI/proguard_common.flags
+++ b/packages/SystemUI/proguard_common.flags
@@ -38,6 +38,11 @@
 -keepnames class com.android.internal.protolog.** { *; }
 -keepnames class android.hardware.common.** { *; }
 
+# TODO(b/316553881): Statically linking flags into SystemUI and bootclasspath might not be a
+# good idea in the first place
+-keepnames class com.android.window.flags.Flags { public *; }
+
+
 # Allows proguard to make private and protected methods and fields public as
 # part of optimization. This lets proguard inline trivial getter/setter methods.
 -allowaccessmodification
diff --git a/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml b/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml
index 7b90270..50d5607 100644
--- a/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml
+++ b/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml
@@ -15,7 +15,7 @@
   ~
   -->
 
-<FrameLayout
+<androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:sysui="http://schemas.android.com/apk/res-auto"
     android:id="@+id/alternate_bouncer"
@@ -32,4 +32,4 @@
         android:importantForAccessibility="no"
         sysui:ignoreRightInset="true"
     />
-</FrameLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
index a8017f6..25cadfb 100644
--- a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
+++ b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
@@ -39,7 +39,6 @@
                 android:adjustViewBounds="true"
                 android:layout_width="wrap_content"
                 android:src="@drawable/ic_activity_down"
-                android:visibility="gone"
                 android:paddingEnd="2dp"
                 />
             <ImageView
@@ -49,7 +48,6 @@
                 android:layout_width="wrap_content"
                 android:src="@drawable/ic_activity_up"
                 android:paddingEnd="2dp"
-                android:visibility="gone"
                 />
         </FrameLayout>
         <FrameLayout
diff --git a/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml b/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml
index 4c5cd7d..e8b8922 100644
--- a/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml
+++ b/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml
@@ -37,7 +37,6 @@
                 android:adjustViewBounds="true"
                 android:layout_width="wrap_content"
                 android:src="@drawable/ic_activity_down"
-                android:visibility="gone"
                 android:paddingEnd="2dp"
             />
             <ImageView
@@ -47,7 +46,6 @@
                 android:layout_width="wrap_content"
                 android:src="@drawable/ic_activity_up"
                 android:paddingEnd="2dp"
-                android:visibility="gone"
             />
         </FrameLayout>
         <FrameLayout
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index ca0fb85..dca84b9 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -26,6 +26,12 @@
     android:layout_height="match_parent"
     android:fitsSystemWindows="true">
 
+    <!-- Placeholder for the communal UI that will be replaced if the feature is enabled. -->
+    <ViewStub
+        android:id="@+id/communal_ui_stub"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
     <com.android.systemui.scrim.ScrimView
         android:id="@+id/scrim_behind"
         android:layout_width="match_parent"
@@ -64,12 +70,6 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
 
-    <!-- Placeholder for the communal UI that will be replaced if the feature is enabled. -->
-    <ViewStub
-        android:id="@+id/communal_ui_stub"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent" />
-
     <include layout="@layout/brightness_mirror_container" />
 
     <com.android.systemui.scrim.ScrimView
@@ -116,6 +116,10 @@
         android:inflatedId="@+id/multi_shade"
         android:layout="@layout/multi_shade" />
 
+    <include layout="@layout/alternate_bouncer"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
     <com.android.systemui.biometrics.AuthRippleView
         android:id="@+id/auth_ripple"
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/widget_picker.xml b/packages/SystemUI/res/layout/widget_picker.xml
index 827bd5d..21dc224 100644
--- a/packages/SystemUI/res/layout/widget_picker.xml
+++ b/packages/SystemUI/res/layout/widget_picker.xml
@@ -14,13 +14,17 @@
   ~ limitations under the License.
   -->
 
-<LinearLayout
+<HorizontalScrollView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/widgets_container"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:padding="64dp"
-    android:gravity="center_vertical"
-    android:orientation="horizontal">
+    android:layout_height="match_parent">
 
-</LinearLayout>
+    <LinearLayout
+        android:id="@+id/widgets_container"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:gravity="center_vertical"
+        android:orientation="horizontal">
+    </LinearLayout>
+
+</HorizontalScrollView>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 33a0a06..90cc1fb 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -543,12 +543,16 @@
 
     <!--  ID for the camera of outer display that needs extra protection -->
     <string translatable="false" name="config_protectedCameraId"></string>
+    <!--  Physical ID for the camera of outer display that needs extra protection -->
+    <string translatable="false" name="config_protectedPhysicalCameraId"></string>
 
     <!-- Similar to config_frontBuiltInDisplayCutoutProtection but for inner display. -->
     <string translatable="false" name="config_innerBuiltInDisplayCutoutProtection"></string>
 
     <!-- ID for the camera of inner display that needs extra protection -->
     <string translatable="false" name="config_protectedInnerCameraId"></string>
+    <!-- Physical ID for the camera of inner display that needs extra protection -->
+    <string translatable="false" name="config_protectedInnerPhysicalCameraId"></string>
 
     <!-- Comma-separated list of packages to exclude from camera protection e.g.
     "com.android.systemui,com.android.xyz" -->
@@ -962,14 +966,6 @@
     <bool name="config_edgeToEdgeBottomSheetDialog">true</bool>
 
     <!--
-    Whether the scene container framework is enabled.
-
-    The scene container framework is a newer (2023) way to organize the various "scenes" between the
-    bouncer, lockscreen, shade, and quick settings.
-    -->
-    <bool name="config_sceneContainerFrameworkEnabled">true</bool>
-
-    <!--
     Time in milliseconds the user has to touch the side FPS sensor to successfully authenticate
     TODO(b/302332976) Get this value from the HAL if they can provide an API for it.
     -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index b947801..0267454 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1905,4 +1905,7 @@
     <!-- Bouncer user switcher margins -->
     <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen>
     <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen>
+
+    <!-- UDFPS view attributes -->
+    <dimen name="udfps_icon_size">6mm</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index cf63cc7..6cb2d45 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -259,4 +259,7 @@
     <item type="id" name="device_entry_icon_view" />
     <item type="id" name="device_entry_icon_fg" />
     <item type="id" name="device_entry_icon_bg" />
+
+    <!--Id for the device-entry UDFPS icon that lives in the alternate bouncer. -->
+    <item type="id" name="alternate_bouncer_udfps_icon_view" />
 </resources>
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
index 8ad32b4..a00cdfa 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
@@ -30,6 +30,10 @@
     fun isUdfps(): Boolean {
         return (this == UDFPS_OPTICAL) || (this == UDFPS_ULTRASONIC)
     }
+
+    fun isPowerButton(): Boolean {
+        return this == POWER_BUTTON
+    }
 }
 
 /** Convert [this] to corresponding [FingerprintSensorType] */
diff --git a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
index d33d279..5f5cca8 100644
--- a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
@@ -38,26 +38,75 @@
     excludedPackages: String,
     private val executor: Executor
 ) {
+    private var activeProtectionInfo: CameraProtectionInfo? = null
+    private var openCamera: OpenCameraInfo? = null
+    private val unavailablePhysicalCameras = mutableSetOf<String>()
     private val excludedPackageIds: Set<String>
     private val listeners = mutableListOf<CameraTransitionCallback>()
     private val availabilityCallback: CameraManager.AvailabilityCallback =
-            object : CameraManager.AvailabilityCallback() {
-                override fun onCameraClosed(cameraId: String) {
-                    cameraProtectionInfoList.forEach {
-                        if (cameraId == it.cameraId) {
-                            notifyCameraInactive()
-                        }
-                    }
+        object : CameraManager.AvailabilityCallback() {
+            override fun onCameraClosed(logicalCameraId: String) {
+                openCamera = null
+                if (activeProtectionInfo?.logicalCameraId == logicalCameraId) {
+                    notifyCameraInactive()
                 }
+                activeProtectionInfo = null
+            }
 
-                override fun onCameraOpened(cameraId: String, packageId: String) {
-                    cameraProtectionInfoList.forEach {
-                        if (cameraId == it.cameraId && !isExcluded(packageId)) {
-                            notifyCameraActive(it)
-                        }
-                    }
+            override fun onCameraOpened(logicalCameraId: String, packageId: String) {
+                openCamera = OpenCameraInfo(logicalCameraId, packageId)
+                if (isExcluded(packageId)) {
+                    return
                 }
-    }
+                val protectionInfo =
+                    cameraProtectionInfoList.firstOrNull {
+                        logicalCameraId == it.logicalCameraId &&
+                            it.physicalCameraId !in unavailablePhysicalCameras
+                    }
+                if (protectionInfo != null) {
+                    activeProtectionInfo = protectionInfo
+                    notifyCameraActive(protectionInfo)
+                }
+            }
+
+            override fun onPhysicalCameraAvailable(
+                logicalCameraId: String,
+                physicalCameraId: String
+            ) {
+                unavailablePhysicalCameras -= physicalCameraId
+                val openCamera = this@CameraAvailabilityListener.openCamera ?: return
+                if (openCamera.logicalCameraId != logicalCameraId) {
+                    return
+                }
+                if (isExcluded(openCamera.packageId)) {
+                    return
+                }
+                val newActiveInfo =
+                    cameraProtectionInfoList.find {
+                        it.logicalCameraId == logicalCameraId &&
+                            it.physicalCameraId == physicalCameraId
+                    }
+                if (newActiveInfo != null) {
+                    activeProtectionInfo = newActiveInfo
+                    notifyCameraActive(newActiveInfo)
+                }
+            }
+
+            override fun onPhysicalCameraUnavailable(
+                logicalCameraId: String,
+                physicalCameraId: String
+            ) {
+                unavailablePhysicalCameras += physicalCameraId
+                val activeInfo = activeProtectionInfo ?: return
+                if (
+                    activeInfo.logicalCameraId == logicalCameraId &&
+                        activeInfo.physicalCameraId == physicalCameraId
+                ) {
+                    activeProtectionInfo = null
+                    notifyCameraInactive()
+                }
+            }
+        }
 
     init {
         excludedPackageIds = excludedPackages.split(",").toSet()
@@ -106,24 +155,21 @@
         listeners.forEach { it.onHideCameraProtection() }
     }
 
-    /**
-     * Callbacks to tell a listener that a relevant camera turned on and off.
-     */
+    /** Callbacks to tell a listener that a relevant camera turned on and off. */
     interface CameraTransitionCallback {
         fun onApplyCameraProtection(protectionPath: Path, bounds: Rect)
+
         fun onHideCameraProtection()
     }
 
     companion object Factory {
         fun build(context: Context, executor: Executor): CameraAvailabilityListener {
-            val manager = context
-                    .getSystemService(Context.CAMERA_SERVICE) as CameraManager
+            val manager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
             val res = context.resources
             val cameraProtectionInfoList = loadCameraProtectionInfoList(res)
             val excluded = res.getString(R.string.config_cameraProtectionExcludedPackages)
 
-            return CameraAvailabilityListener(
-                    manager, cameraProtectionInfoList, excluded, executor)
+            return CameraAvailabilityListener(manager, cameraProtectionInfoList, excluded, executor)
         }
 
         private fun pathFromString(pathString: String): Path {
@@ -140,19 +186,23 @@
 
         private fun loadCameraProtectionInfoList(res: Resources): List<CameraProtectionInfo> {
             val list = mutableListOf<CameraProtectionInfo>()
-            val front = loadCameraProtectionInfo(
+            val front =
+                loadCameraProtectionInfo(
                     res,
                     R.string.config_protectedCameraId,
+                    R.string.config_protectedPhysicalCameraId,
                     R.string.config_frontBuiltInDisplayCutoutProtection
-            )
+                )
             if (front != null) {
                 list.add(front)
             }
-            val inner = loadCameraProtectionInfo(
+            val inner =
+                loadCameraProtectionInfo(
                     res,
                     R.string.config_protectedInnerCameraId,
+                    R.string.config_protectedInnerPhysicalCameraId,
                     R.string.config_innerBuiltInDisplayCutoutProtection
-            )
+                )
             if (inner != null) {
                 list.add(inner)
             }
@@ -160,30 +210,44 @@
         }
 
         private fun loadCameraProtectionInfo(
-                res: Resources,
-                cameraIdRes: Int,
-                pathRes: Int
+            res: Resources,
+            cameraIdRes: Int,
+            physicalCameraIdRes: Int,
+            pathRes: Int
         ): CameraProtectionInfo? {
-            val cameraId = res.getString(cameraIdRes)
-            if (cameraId == null || cameraId.isEmpty()) {
+            val logicalCameraId = res.getString(cameraIdRes)
+            if (logicalCameraId.isNullOrEmpty()) {
                 return null
             }
+            val physicalCameraId = res.getString(physicalCameraIdRes)
             val protectionPath = pathFromString(res.getString(pathRes))
             val computed = RectF()
             protectionPath.computeBounds(computed)
-            val protectionBounds = Rect(
+            val protectionBounds =
+                Rect(
                     computed.left.roundToInt(),
                     computed.top.roundToInt(),
                     computed.right.roundToInt(),
                     computed.bottom.roundToInt()
+                )
+            return CameraProtectionInfo(
+                logicalCameraId,
+                physicalCameraId,
+                protectionPath,
+                protectionBounds
             )
-            return CameraProtectionInfo(cameraId, protectionPath, protectionBounds)
         }
     }
 
-    data class CameraProtectionInfo (
-            val cameraId: String,
-            val cutoutProtectionPath: Path,
-            val cutoutBounds: Rect
+    data class CameraProtectionInfo(
+        val logicalCameraId: String,
+        val physicalCameraId: String?,
+        val cutoutProtectionPath: Path,
+        val cutoutBounds: Rect,
+    )
+
+    private data class OpenCameraInfo(
+        val logicalCameraId: String,
+        val packageId: String,
     )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 6faee8c..008de43 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -179,6 +179,7 @@
     @VisibleForTesting
     protected DisplayInfo mDisplayInfo = new DisplayInfo();
     private DisplayCutout mDisplayCutout;
+    private boolean mPendingManualConfigUpdate;
 
     @VisibleForTesting
     protected void showCameraProtection(@NonNull Path protectionPath, @NonNull Rect bounds) {
@@ -571,6 +572,11 @@
                         setupDecorations();
                         return;
                     }
+
+                    if (mPendingManualConfigUpdate) {
+                        mPendingManualConfigUpdate = false;
+                        onConfigurationChanged(mContext.getResources().getConfiguration());
+                    }
                 }
             }
         };
@@ -1064,6 +1070,15 @@
 
         mExecutor.execute(() -> {
             Trace.beginSection("ScreenDecorations#onConfigurationChanged");
+            mContext.getDisplay().getDisplayInfo(mDisplayInfo);
+            if (displaySizeChanged(mDisplaySize, mDisplayInfo)) {
+                // We expect the display change event to happen first, but in this case, we received
+                // onConfigurationChanged first.
+                // Return so that we handle the display change event first, and then manually
+                // trigger the config update.
+                mPendingManualConfigUpdate = true;
+                return;
+            }
             int oldRotation = mRotation;
             mPendingConfigChange = false;
             updateConfiguration();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
index 24aa11e..8c2d221 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
@@ -18,6 +18,8 @@
 
 import com.android.systemui.accessibility.data.repository.ColorCorrectionRepository
 import com.android.systemui.accessibility.data.repository.ColorCorrectionRepositoryImpl
+import com.android.systemui.accessibility.data.repository.ColorInversionRepository
+import com.android.systemui.accessibility.data.repository.ColorInversionRepositoryImpl
 import com.android.systemui.accessibility.qs.QSAccessibilityModule
 import dagger.Binds
 import dagger.Module
@@ -25,7 +27,8 @@
 @Module(includes = [QSAccessibilityModule::class])
 interface AccessibilityModule {
     @Binds
-    abstract fun colorCorrectionRepository(
-        impl: ColorCorrectionRepositoryImpl
-    ): ColorCorrectionRepository
+    fun colorCorrectionRepository(impl: ColorCorrectionRepositoryImpl): ColorCorrectionRepository
+
+    @Binds
+    fun colorInversionRepository(impl: ColorInversionRepositoryImpl): ColorInversionRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt
new file mode 100644
index 0000000..bbf10c5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.os.UserHandle
+import android.provider.Settings.Secure
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.withContext
+
+/** Provides data related to color inversion. */
+interface ColorInversionRepository {
+    /** Observable for whether color inversion is enabled */
+    fun isEnabled(userHandle: UserHandle): Flow<Boolean>
+
+    /** Sets color inversion enabled state. */
+    suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean
+}
+
+@SysUISingleton
+class ColorInversionRepositoryImpl
+@Inject
+constructor(
+    @Background private val bgCoroutineContext: CoroutineContext,
+    private val secureSettings: SecureSettings,
+) : ColorInversionRepository {
+
+    override fun isEnabled(userHandle: UserHandle): Flow<Boolean> =
+        secureSettings
+            .observerFlow(userHandle.identifier, SETTING_NAME)
+            .onStart { emit(Unit) }
+            .map { secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED }
+            .distinctUntilChanged()
+            .flowOn(bgCoroutineContext)
+
+    override suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean =
+        withContext(bgCoroutineContext) {
+            secureSettings.putIntForUser(
+                SETTING_NAME,
+                if (isEnabled) ENABLED else DISABLED,
+                userHandle.identifier
+            )
+        }
+
+    companion object {
+        private const val SETTING_NAME = Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED
+        private const val DISABLED = 0
+        private const val ENABLED = 1
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
index df7fdb8..135ab35 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
@@ -31,6 +31,10 @@
 import com.android.systemui.qs.tiles.impl.colorcorrection.domain.interactor.ColorCorrectionTileDataInteractor
 import com.android.systemui.qs.tiles.impl.colorcorrection.domain.interactor.ColorCorrectionUserActionInteractor
 import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel
+import com.android.systemui.qs.tiles.impl.inversion.domain.ColorInversionTileMapper
+import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionTileDataInteractor
+import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionUserActionInteractor
+import com.android.systemui.qs.tiles.impl.inversion.domain.model.ColorInversionTileModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
@@ -87,8 +91,8 @@
     fun bindFontScalingTile(fontScalingTile: FontScalingTile): QSTileImpl<*>
 
     companion object {
-
         const val COLOR_CORRECTION_TILE_SPEC = "color_correction"
+        const val COLOR_INVERSION_TILE_SPEC = "inversion"
 
         @Provides
         @IntoMap
@@ -120,5 +124,36 @@
                 stateInteractor,
                 mapper,
             )
+
+        @Provides
+        @IntoMap
+        @StringKey(COLOR_INVERSION_TILE_SPEC)
+        fun provideColorInversionTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+            QSTileConfig(
+                tileSpec = TileSpec.create(COLOR_INVERSION_TILE_SPEC),
+                uiConfig =
+                    QSTileUIConfig.Resource(
+                        iconRes = R.drawable.qs_invert_colors_icon_off,
+                        labelRes = R.string.quick_settings_inversion_label,
+                    ),
+                instanceId = uiEventLogger.getNewInstanceId(),
+            )
+
+        /** Inject ColorInversionTile into tileViewModelMap in QSModule */
+        @Provides
+        @IntoMap
+        @StringKey(COLOR_INVERSION_TILE_SPEC)
+        fun provideColorInversionTileViewModel(
+            factory: QSTileViewModelFactory.Static<ColorInversionTileModel>,
+            mapper: ColorInversionTileMapper,
+            stateInteractor: ColorInversionTileDataInteractor,
+            userActionInteractor: ColorInversionUserActionInteractor
+        ): QSTileViewModel =
+            factory.create(
+                TileSpec.create(COLOR_INVERSION_TILE_SPEC),
+                userActionInteractor,
+                stateInteractor,
+                mapper,
+            )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
index 38043b4..f4a2811 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.biometrics.domain.interactor
 
+import android.content.Context
 import android.view.MotionEvent
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
@@ -23,12 +24,15 @@
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.res.R
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
 /** Encapsulates business logic for interacting with the UDFPS overlay. */
@@ -36,10 +40,12 @@
 class UdfpsOverlayInteractor
 @Inject
 constructor(
+    @Application context: Context,
     private val authController: AuthController,
     private val selectedUserInteractor: SelectedUserInteractor,
     @Application scope: CoroutineScope
 ) {
+    private val iconSize: Int = context.resources.getDimensionPixelSize(R.dimen.udfps_icon_size)
 
     /** Whether a touch is within the under-display fingerprint sensor area */
     fun isTouchWithinUdfpsArea(ev: MotionEvent): Boolean {
@@ -70,6 +76,14 @@
             }
             .stateIn(scope, started = SharingStarted.Eagerly, initialValue = UdfpsOverlayParams())
 
+    // Padding between the fingerprint icon and its bounding box in pixels.
+    val iconPadding: Flow<Int> =
+        udfpsOverlayParams.map { params ->
+            val sensorWidth = params.nativeSensorBounds.right - params.nativeSensorBounds.left
+            val nativePadding = (sensorWidth - iconSize) / 2
+            (nativePadding * params.scaleFactor).toInt()
+        }
+
     companion object {
         private const val TAG = "UdfpsOverlayInteractor"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModel.kt
index c19ea19..7106674 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModel.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.biometrics.ui.viewmodel
 
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
 import com.android.systemui.statusbar.phone.hideAffordancesRequest
@@ -26,22 +27,30 @@
 
 /**
  * View model for the UdfpsTouchOverlay for when UDFPS is being requested for device entry. Handles
- * touches as long as the device entry views are visible.
+ * touches as long as the device entry view is visible (the lockscreen or the alternate bouncer
+ * view).
  */
 @ExperimentalCoroutinesApi
 class DeviceEntryUdfpsTouchOverlayViewModel
 @Inject
 constructor(
     deviceEntryIconViewModel: DeviceEntryIconViewModel,
+    alternateBouncerInteractor: AlternateBouncerInteractor,
     systemUIDialogManager: SystemUIDialogManager,
 ) : UdfpsTouchOverlayViewModel {
-    // TODO (b/305234447): AlternateBouncer showing overrides sysuiDialogHideAffordancesRequest
-    override val shouldHandleTouches: Flow<Boolean> =
+    private val showingUdfpsAffordance: Flow<Boolean> =
         combine(
             deviceEntryIconViewModel.deviceEntryViewAlpha,
+            alternateBouncerInteractor.isVisible,
+        ) { deviceEntryViewAlpha, alternateBouncerVisible ->
+            deviceEntryViewAlpha > ALLOW_TOUCH_ALPHA_THRESHOLD || alternateBouncerVisible
+        }
+    override val shouldHandleTouches: Flow<Boolean> =
+        combine(
+            showingUdfpsAffordance,
             systemUIDialogManager.hideAffordancesRequest,
-        ) { deviceEntryViewAlpha, dialogRequestingHideAffordances ->
-            deviceEntryViewAlpha > ALLOW_TOUCH_ALPHA_THRESHOLD && !dialogRequestingHideAffordances
+        ) { showingUdfpsAffordance, dialogRequestingHideAffordances ->
+            showingUdfpsAffordance && !dialogRequestingHideAffordances
         }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
index 84f7dd5..af32eb5 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
@@ -29,9 +29,10 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 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.map
+import kotlinx.coroutines.flow.stateIn
 
 /** Encapsulates business logic for interacting with the lock-screen alternate bouncer. */
 @SysUISingleton
@@ -52,19 +53,13 @@
     private val alternateBouncerUiAvailableFromSource: HashSet<String> = HashSet()
     private val alternateBouncerSupported: StateFlow<Boolean> =
         if (DeviceEntryUdfpsRefactor.isEnabled) {
-            // The device entry udfps refactor doesn't currently support the alternate bouncer.
-            // TODO: Re-enable when b/287599719 is ready.
-            MutableStateFlow(false).asStateFlow()
-            //            fingerprintPropertyRepository.sensorType
-            //                .map { sensorType ->
-            //                    sensorType.isUdfps() || sensorType ==
-            // FingerprintSensorType.POWER_BUTTON
-            //                }
-            //                .stateIn(
-            //                    scope = scope,
-            //                    started = SharingStarted.Eagerly,
-            //                    initialValue = false,
-            //                )
+            fingerprintPropertyRepository.sensorType
+                .map { sensorType -> sensorType.isUdfps() || sensorType.isPowerButton() }
+                .stateIn(
+                    scope = scope,
+                    started = SharingStarted.Eagerly,
+                    initialValue = false,
+                )
         } else {
             bouncerRepository.alternateBouncerUIAvailable
         }
diff --git a/packages/SystemUI/src/com/android/systemui/common/domain/CommonDomainLayerModule.kt b/packages/SystemUI/src/com/android/systemui/common/domain/CommonDomainLayerModule.kt
deleted file mode 100644
index 7be2eaf..0000000
--- a/packages/SystemUI/src/com/android/systemui/common/domain/CommonDomainLayerModule.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.common.domain
-
-import com.android.systemui.common.domain.interactor.ConfigurationInteractor
-import com.android.systemui.common.domain.interactor.ConfigurationInteractorImpl
-import dagger.Binds
-import dagger.Module
-
-@Module
-abstract class CommonDomainLayerModule {
-    @Binds abstract fun bindInteractor(impl: ConfigurationInteractorImpl): ConfigurationInteractor
-}
diff --git a/packages/SystemUI/src/com/android/systemui/common/domain/interactor/ConfigurationInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/domain/interactor/ConfigurationInteractor.kt
deleted file mode 100644
index 89053d1..0000000
--- a/packages/SystemUI/src/com/android/systemui/common/domain/interactor/ConfigurationInteractor.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-package com.android.systemui.common.domain.interactor
-
-import android.content.res.Configuration
-import android.graphics.Rect
-import android.view.Surface
-import com.android.systemui.common.ui.data.repository.ConfigurationRepository
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.map
-
-interface ConfigurationInteractor {
-    /**
-     * Returns screen size adjusted to rotation, so returned screen sizes are stable across all
-     * rotations, could be useful if you need to react to screen resize (e.g. fold/unfold on
-     * foldable devices)
-     */
-    val naturalMaxBounds: Flow<Rect>
-}
-
-class ConfigurationInteractorImpl
-@Inject
-constructor(private val repository: ConfigurationRepository) : ConfigurationInteractor {
-
-    override val naturalMaxBounds: Flow<Rect>
-        get() = repository.configurationValues.map { it.naturalScreenBounds }.distinctUntilChanged()
-
-    /**
-     * Returns screen size adjusted to rotation, so returned screen size is stable across all
-     * rotations
-     */
-    private val Configuration.naturalScreenBounds: Rect
-        get() {
-            val rotation = windowConfiguration.displayRotation
-            val maxBounds = windowConfiguration.maxBounds
-            return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
-                Rect(0, 0, maxBounds.width(), maxBounds.height())
-            } else {
-                Rect(0, 0, maxBounds.height(), maxBounds.width())
-            }
-        }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
index 3648f3b..1353985 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
@@ -17,17 +17,45 @@
 
 package com.android.systemui.common.ui.domain.interactor
 
+import android.content.res.Configuration
+import android.graphics.Rect
+import android.view.Surface
 import com.android.systemui.common.ui.data.repository.ConfigurationRepository
 import com.android.systemui.dagger.SysUISingleton
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.onStart
 
 /** Business logic related to configuration changes. */
 @SysUISingleton
 class ConfigurationInteractor @Inject constructor(private val repository: ConfigurationRepository) {
+    /**
+     * Returns screen size adjusted to rotation, so returned screen size is stable across all
+     * rotations
+     */
+    private val Configuration.naturalScreenBounds: Rect
+        get() {
+            val rotation = windowConfiguration.displayRotation
+            val maxBounds = windowConfiguration.maxBounds
+            return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
+                Rect(0, 0, maxBounds.width(), maxBounds.height())
+            } else {
+                Rect(0, 0, maxBounds.height(), maxBounds.width())
+            }
+        }
+
+    /**
+     * Returns screen size adjusted to rotation, so returned screen sizes are stable across all
+     * rotations, could be useful if you need to react to screen resize (e.g. fold/unfold on
+     * foldable devices)
+     */
+    val naturalMaxBounds: Flow<Rect> =
+        repository.configurationValues.map { it.naturalScreenBounds }.distinctUntilChanged()
+
     /** Given [resourceId], emit the dimension pixel size on config change */
     fun dimensionPixelSize(resourceId: Int): Flow<Int> {
         return onAnyConfigurationChange.mapLatest { repository.getDimensionPixelSize(resourceId) }
@@ -43,4 +71,7 @@
     /** Emit an event on any config change */
     val onAnyConfigurationChange: Flow<Unit> =
         repository.onAnyConfigurationChange.onStart { emit(Unit) }
+
+    /** Emits the current resolution scaling factor */
+    val scaleForResolution: Flow<Float> = repository.scaleForResolution
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
index c5610c87..75f9d80 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
@@ -30,9 +30,9 @@
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.UI_SURFACE_GLANCEABLE_HUB
 import com.android.systemui.smartspace.SmartspacePrecondition
 import com.android.systemui.smartspace.SmartspaceTargetFilter
-import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_PRECONDITION
-import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_TARGET_FILTER
 import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.GLANCEABLE_HUB_SMARTSPACE_DATA_PLUGIN
+import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.LOCKSCREEN_SMARTSPACE_PRECONDITION
+import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.LOCKSCREEN_SMARTSPACE_TARGET_FILTER
 import com.android.systemui.util.concurrency.Execution
 import java.util.Optional
 import java.util.concurrent.Executor
@@ -48,8 +48,8 @@
     private val smartspaceManager: SmartspaceManager?,
     private val execution: Execution,
     @Main private val uiExecutor: Executor,
-    @Named(DREAM_SMARTSPACE_PRECONDITION) private val precondition: SmartspacePrecondition,
-    @Named(DREAM_SMARTSPACE_TARGET_FILTER)
+    @Named(LOCKSCREEN_SMARTSPACE_PRECONDITION) private val precondition: SmartspacePrecondition,
+    @Named(LOCKSCREEN_SMARTSPACE_TARGET_FILTER)
     private val optionalTargetFilter: Optional<SmartspaceTargetFilter>,
     @Named(GLANCEABLE_HUB_SMARTSPACE_DATA_PLUGIN) optionalPlugin: Optional<BcSmartspaceDataPlugin>,
 ) {
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 573a748..887b18c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -19,7 +19,9 @@
 import android.appwidget.AppWidgetProviderInfo
 import android.content.Intent
 import android.os.Bundle
+import android.os.RemoteException
 import android.util.Log
+import android.view.IWindowManager
 import androidx.activity.ComponentActivity
 import androidx.activity.result.ActivityResultLauncher
 import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
@@ -34,6 +36,7 @@
 constructor(
     private val communalViewModel: CommunalEditModeViewModel,
     private val communalInteractor: CommunalInteractor,
+    private var windowManagerService: IWindowManager? = null,
 ) : ComponentActivity() {
     companion object {
         /**
@@ -77,8 +80,12 @@
                 )
             },
             onEditDone = {
-                // TODO(b/315154364): in a separate change, lock the device and transition to GH
-                finish()
+                try {
+                    checkNotNull(windowManagerService).lockNow(/* options */ null)
+                    finish()
+                } catch (e: RemoteException) {
+                    Log.e(TAG, "Couldn't lock the device as WindowManager is dead.")
+                }
             }
         )
     }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetPickerActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetPickerActivity.kt
index a276548..a26afc8 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetPickerActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetPickerActivity.kt
@@ -19,13 +19,15 @@
 import android.appwidget.AppWidgetManager
 import android.appwidget.AppWidgetProviderInfo
 import android.content.Intent
+import android.graphics.Color
 import android.os.Bundle
-import android.util.DisplayMetrics
 import android.util.Log
 import android.view.ViewGroup
 import android.widget.ImageView
 import android.widget.LinearLayout
 import androidx.activity.ComponentActivity
+import androidx.core.view.setMargins
+import androidx.core.view.setPadding
 import com.android.systemui.res.R
 import javax.inject.Inject
 
@@ -43,7 +45,6 @@
         super.onCreate(savedInstanceState)
 
         setContentView(R.layout.widget_picker)
-
         loadWidgets()
     }
 
@@ -54,24 +55,38 @@
                 appWidgetManager
                     .getInstalledProviders(AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD)
                     ?.stream()
-                    ?.limit(5)
                     ?.forEach { widgetInfo ->
                         val activity = this@WidgetPickerActivity
-                        val widgetPreview =
-                            widgetInfo.loadPreviewImage(activity, DisplayMetrics.DENSITY_HIGH)
-                        val widgetView = ImageView(activity)
-                        val lp = LinearLayout.LayoutParams(WIDGET_PREVIEW_SIZE, WIDGET_PREVIEW_SIZE)
-                        widgetView.setLayoutParams(lp)
-                        widgetView.setImageDrawable(widgetPreview)
-                        widgetView.setOnClickListener({
-                            setResult(
-                                RESULT_OK,
-                                Intent().putExtra(EditWidgetsActivity.ADD_WIDGET_INFO, widgetInfo)
-                            )
-                            finish()
-                        })
-
-                        addView(widgetView)
+                        (widgetInfo.loadPreviewImage(activity, 0)
+                                ?: widgetInfo.loadIcon(activity, 0))
+                            ?.let {
+                                addView(
+                                    ImageView(activity).also { v ->
+                                        v.setImageDrawable(it)
+                                        v.setBackgroundColor(WIDGET_PREVIEW_BACKGROUND_COLOR)
+                                        v.setPadding(WIDGET_PREVIEW_PADDING)
+                                        v.layoutParams =
+                                            LinearLayout.LayoutParams(
+                                                    WIDGET_PREVIEW_SIZE,
+                                                    WIDGET_PREVIEW_SIZE
+                                                )
+                                                .also { lp ->
+                                                    lp.setMargins(WIDGET_PREVIEW_MARGINS)
+                                                }
+                                        v.setOnClickListener {
+                                            setResult(
+                                                RESULT_OK,
+                                                Intent()
+                                                    .putExtra(
+                                                        EditWidgetsActivity.ADD_WIDGET_INFO,
+                                                        widgetInfo
+                                                    )
+                                            )
+                                            finish()
+                                        }
+                                    }
+                                )
+                            }
                     }
             } catch (e: RuntimeException) {
                 Log.e(TAG, "Exception fetching widget providers", e)
@@ -80,7 +95,10 @@
     }
 
     companion object {
-        private const val WIDGET_PREVIEW_SIZE = 400
+        private const val WIDGET_PREVIEW_SIZE = 600
+        private const val WIDGET_PREVIEW_MARGINS = 32
+        private const val WIDGET_PREVIEW_PADDING = 32
+        private val WIDGET_PREVIEW_BACKGROUND_COLOR = Color.rgb(216, 225, 220)
         private const val TAG = "WidgetPickerActivity"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index ca8268d..a25c788 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -43,7 +43,6 @@
 import com.android.systemui.classifier.FalsingModule;
 import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
 import com.android.systemui.common.data.CommonDataLayerModule;
-import com.android.systemui.common.domain.CommonDomainLayerModule;
 import com.android.systemui.communal.dagger.CommunalModule;
 import com.android.systemui.complication.dagger.ComplicationComponent;
 import com.android.systemui.controls.dagger.ControlsModule;
@@ -179,7 +178,6 @@
         ClockRegistryModule.class,
         CommunalModule.class,
         CommonDataLayerModule.class,
-        CommonDomainLayerModule.class,
         ConnectivityModule.class,
         ControlsModule.class,
         CoroutinesModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
index ca4ef0d..4a8c040 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
@@ -34,9 +34,9 @@
 import com.android.systemui.smartspace.SmartspacePrecondition
 import com.android.systemui.smartspace.SmartspaceTargetFilter
 import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_DATA_PLUGIN
-import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_PRECONDITION
-import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_TARGET_FILTER
 import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_WEATHER_SMARTSPACE_DATA_PLUGIN
+import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.LOCKSCREEN_SMARTSPACE_PRECONDITION
+import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.LOCKSCREEN_SMARTSPACE_TARGET_FILTER
 import com.android.systemui.smartspace.dagger.SmartspaceViewComponent
 import com.android.systemui.util.concurrency.Execution
 import java.util.Optional
@@ -54,8 +54,8 @@
     private val execution: Execution,
     @Main private val uiExecutor: Executor,
     private val smartspaceViewComponentFactory: SmartspaceViewComponent.Factory,
-    @Named(DREAM_SMARTSPACE_PRECONDITION) private val precondition: SmartspacePrecondition,
-    @Named(DREAM_SMARTSPACE_TARGET_FILTER)
+    @Named(LOCKSCREEN_SMARTSPACE_PRECONDITION) private val precondition: SmartspacePrecondition,
+    @Named(LOCKSCREEN_SMARTSPACE_TARGET_FILTER)
     private val optionalTargetFilter: Optional<SmartspaceTargetFilter>,
     @Named(DREAM_SMARTSPACE_DATA_PLUGIN) optionalPlugin: Optional<BcSmartspaceDataPlugin>,
     @Named(DREAM_WEATHER_SMARTSPACE_DATA_PLUGIN)
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 5ec51f4..1540423 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -25,7 +25,9 @@
 import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
 import com.android.systemui.Flags.keyguardBottomAreaRefactor
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
@@ -36,20 +38,28 @@
 class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, handler: Handler) :
     FlagDependenciesBase(featureFlags, handler) {
     override fun defineDependencies() {
+        // Internal notification backend dependencies
+        crossAppPoliteNotifications dependsOn politeNotifications
+        vibrateWhileUnlockedToken dependsOn politeNotifications
+
+        // Internal notification frontend dependencies
         NotificationsLiveDataStoreRefactor.token dependsOn NotificationIconContainerRefactor.token
         FooterViewRefactor.token dependsOn NotificationIconContainerRefactor.token
 
-        val keyguardBottomAreaRefactor = FlagToken(
-                FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor())
+        // Internal keyguard dependencies
         KeyguardShadeMigrationNssl.token dependsOn keyguardBottomAreaRefactor
 
-        val crossAppPoliteNotifToken =
-                FlagToken(FLAG_CROSS_APP_POLITE_NOTIFICATIONS, crossAppPoliteNotifications())
-        val politeNotifToken = FlagToken(FLAG_POLITE_NOTIFICATIONS, politeNotifications())
-        crossAppPoliteNotifToken dependsOn politeNotifToken
-
-        val vibrateWhileUnlockedToken =
-                FlagToken(FLAG_VIBRATE_WHILE_UNLOCKED, vibrateWhileUnlocked())
-        vibrateWhileUnlockedToken dependsOn politeNotifToken
+        // SceneContainer dependencies
+        SceneContainerFlag.getFlagDependencies().forEach { (alpha, beta) -> alpha dependsOn beta }
+        SceneContainerFlag.getMainStaticFlag() dependsOn MIGRATE_KEYGUARD_STATUS_BAR_VIEW
     }
+
+    private inline val politeNotifications
+        get() = FlagToken(FLAG_POLITE_NOTIFICATIONS, politeNotifications())
+    private inline val crossAppPoliteNotifications
+        get() = FlagToken(FLAG_CROSS_APP_POLITE_NOTIFICATIONS, crossAppPoliteNotifications())
+    private inline val vibrateWhileUnlockedToken: FlagToken
+        get() = FlagToken(FLAG_VIBRATE_WHILE_UNLOCKED, vibrateWhileUnlocked())
+    private inline val keyguardBottomAreaRefactor
+        get() = FlagToken(FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor())
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt
index e96660f..6560ee3 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt
@@ -133,6 +133,7 @@
                 .setContentTitle(title)
                 .setContentText(details)
                 .setStyle(Notification.BigTextStyle().bigText(details))
+                .setVisibility(Notification.VISIBILITY_PUBLIC)
                 .build()
         notifManager.createNotificationChannel(channel)
         notifManager.notify("flags", 0, notification)
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index b1d4587..b43f54d 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -564,6 +564,13 @@
     val ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS =
         unreleasedFlag("enable_unfold_status_bar_animations")
 
+    // TODO(b/316157842): Tracking Bug
+    // Adds extra delay to notifications measure
+    @Keep
+    @JvmField
+    val ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE =
+        unreleasedFlag("enable_notifications_simulate_slow_measure")
+
     // TODO(b259590361): Tracking bug
     val EXPERIMENTAL_FLAG = unreleasedFlag("exp_flag_release")
 
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 45433e6..3de9e68 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -1054,6 +1054,7 @@
                 return;
             }
             Trace.instantForTrack(Trace.TRACE_TAG_APP, "bugreport", "BugReportAction#onPress");
+            Log.d(TAG, "BugReportAction#onPress");
             // Add a little delay before executing, to give the
             // dialog a chance to go away before it takes a
             // screenshot.
@@ -1069,6 +1070,7 @@
                             Log.w(TAG, "Bugreport handler could not be launched");
                             Trace.instantForTrack(Trace.TRACE_TAG_APP, "bugreport",
                                     "BugReportAction#requestingInteractiveBugReport");
+                            Log.d(TAG, "BugReportAction#requestingInteractiveBugReport");
                             mIActivityManager.requestInteractiveBugReport();
                         }
                     } catch (RemoteException e) {
@@ -1090,6 +1092,7 @@
                 mUiEventLogger.log(GlobalActionsEvent.GA_BUGREPORT_LONG_PRESS);
                 Trace.instantForTrack(Trace.TRACE_TAG_APP, "bugreport",
                         "BugReportAction#requestingFullBugReport");
+                Log.d(TAG, "BugReportAction#requestingFullBugReport");
                 mIActivityManager.requestFullBugReport();
             } catch (RemoteException e) {
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index 7e360cf..52f2759 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -141,5 +141,6 @@
     companion object {
         val TRANSITION_DURATION_MS = 300.milliseconds
         val TO_GONE_DURATION = 500.milliseconds
+        val TO_AOD_DURATION = TRANSITION_DURATION_MS
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
index cae6147..f5bcab9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
@@ -80,6 +80,11 @@
             return state != GONE
         }
 
+        /** Whether either of the bouncers are visible when we're FINISHED in the given state. */
+        fun isBouncerState(state: KeyguardState): Boolean {
+            return state == PRIMARY_BOUNCER || state == ALTERNATE_BOUNCER
+        }
+
         /**
          * Whether the device is awake ([PowerInteractor.isAwake]) when we're FINISHED in the given
          * keyguard state.
@@ -107,4 +112,4 @@
             return !deviceIsAwakeInState(state)
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt
new file mode 100644
index 0000000..d12d193
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.content.res.ColorStateList
+import android.view.View
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+object AlternateBouncerUdfpsViewBinder {
+
+    /** Updates UI for the UDFPS icon on the alternate bouncer. */
+    @JvmStatic
+    fun bind(
+        view: DeviceEntryIconView,
+        viewModel: AlternateBouncerUdfpsIconViewModel,
+    ) {
+        if (DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()) {
+            return
+        }
+        val fgIconView = view.iconView
+        val bgView = view.bgView
+
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                viewModel.accessibilityDelegateHint.collect { hint ->
+                    view.accessibilityHintType = hint
+                }
+            }
+        }
+
+        fgIconView.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                viewModel.fgViewModel.collect { fgViewModel ->
+                    fgIconView.setImageState(
+                        view.getIconState(fgViewModel.type, fgViewModel.useAodVariant),
+                        /* merge */ false
+                    )
+                    fgIconView.imageTintList = ColorStateList.valueOf(fgViewModel.tint)
+                    fgIconView.setPadding(
+                        fgViewModel.padding,
+                        fgViewModel.padding,
+                        fgViewModel.padding,
+                        fgViewModel.padding,
+                    )
+                }
+            }
+        }
+
+        bgView.visibility = View.VISIBLE
+        bgView.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                viewModel.bgViewModel.collect { bgViewModel ->
+                    bgView.alpha = bgViewModel.alpha
+                    bgView.imageTintList = ColorStateList.valueOf(bgViewModel.tint)
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
index d1d323f..78906ac 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
@@ -17,47 +17,49 @@
 package com.android.systemui.keyguard.ui.binder
 
 import android.view.View
-import android.view.ViewGroup
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.classifier.Classifier
 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel
 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
 import com.android.systemui.scrim.ScrimView
-import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.gesture.TapGestureDetector
-import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.launch
 
-/** Binds the alternate bouncer view to its view-model. */
+/**
+ * Binds the alternate bouncer view to its view-model.
+ *
+ * For devices that support UDFPS, this includes a UDFPS view.
+ */
 @ExperimentalCoroutinesApi
 object AlternateBouncerViewBinder {
 
     /** Binds the view to the view-model, continuing to update the former based on the latter. */
     @JvmStatic
     fun bind(
-        view: ViewGroup,
+        view: ConstraintLayout,
         viewModel: AlternateBouncerViewModel,
-        scope: CoroutineScope,
-        notificationShadeWindowController: NotificationShadeWindowController,
         falsingManager: FalsingManager,
         swipeUpAnywhereGestureHandler: SwipeUpAnywhereGestureHandler,
         tapGestureDetector: TapGestureDetector,
+        alternateBouncerUdfpsIconViewModel: AlternateBouncerUdfpsIconViewModel,
     ) {
-        if (DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()) return
-        scope.launch {
-            // forcePluginOpen is necessary to show over occluded apps.
-            // This cannot be tied to the view's lifecycle because setting this allows the view
-            // to be started in the first place.
-            viewModel.forcePluginOpen.collect {
-                notificationShadeWindowController.setForcePluginOpen(it, this)
-            }
+        if (DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()) {
+            return
         }
+        optionallyAddUdfpsView(
+            view = view,
+            alternateBouncerUdfpsIconViewModel = alternateBouncerUdfpsIconViewModel,
+        )
 
         val scrim = view.requireViewById(R.id.alternate_bouncer_scrim) as ScrimView
         view.repeatWhenAttached { alternateBouncerViewContainer ->
@@ -99,6 +101,52 @@
             }
         }
     }
+
+    private fun optionallyAddUdfpsView(
+        view: ConstraintLayout,
+        alternateBouncerUdfpsIconViewModel: AlternateBouncerUdfpsIconViewModel,
+    ) {
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.CREATED) {
+                launch {
+                    alternateBouncerUdfpsIconViewModel.iconLocation.collect { iconLocation ->
+                        val viewId = R.id.alternate_bouncer_udfps_icon_view
+                        var udfpsView = view.getViewById(viewId)
+                        if (udfpsView == null) {
+                            udfpsView =
+                                DeviceEntryIconView(view.context, null).apply { id = viewId }
+                            view.addView(udfpsView)
+                            AlternateBouncerUdfpsViewBinder.bind(
+                                udfpsView,
+                                alternateBouncerUdfpsIconViewModel,
+                            )
+                        }
+
+                        val constraintSet = ConstraintSet().apply { clone(view) }
+                        constraintSet.apply {
+                            constrainWidth(viewId, iconLocation.width)
+                            constrainHeight(viewId, iconLocation.height)
+                            connect(
+                                viewId,
+                                ConstraintSet.TOP,
+                                ConstraintSet.PARENT_ID,
+                                ConstraintSet.TOP,
+                                iconLocation.top,
+                            )
+                            connect(
+                                viewId,
+                                ConstraintSet.START,
+                                ConstraintSet.PARENT_ID,
+                                ConstraintSet.START,
+                                iconLocation.left
+                            )
+                        }
+                        constraintSet.applyTo(view)
+                    }
+                }
+            }
+        }
+    }
 }
 
 private const val swipeTag = "AlternateBouncer-SWIPE"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 01a1ca3..08e2a8f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -393,7 +393,6 @@
         iconsAppearTranslationPx: Int,
         screenOffAnimationController: ScreenOffAnimationController,
     ) {
-        val statusViewMigrated = KeyguardShadeMigrationNssl.isEnabled
         animate().cancel()
         val animatorListener =
             object : AnimatorListenerAdapter() {
@@ -404,13 +403,13 @@
         when {
             !isVisible.isAnimating -> {
                 alpha = 1f
-                if (!statusViewMigrated) {
+                if (!KeyguardShadeMigrationNssl.isEnabled) {
                     translationY = 0f
                 }
                 visibility = if (isVisible.value) View.VISIBLE else View.INVISIBLE
             }
             newAodTransition() -> {
-                animateInIconTranslation(statusViewMigrated)
+                animateInIconTranslation()
                 if (isVisible.value) {
                     CrossFadeHelper.fadeIn(this, animatorListener)
                 } else {
@@ -419,7 +418,7 @@
             }
             !isVisible.value -> {
                 // Let's make sure the icon are translated to 0, since we cancelled it above
-                animateInIconTranslation(statusViewMigrated)
+                animateInIconTranslation()
                 CrossFadeHelper.fadeOut(this, animatorListener)
             }
             visibility != View.VISIBLE -> {
@@ -429,13 +428,12 @@
                 appearIcons(
                     animate = screenOffAnimationController.shouldAnimateAodIcons(),
                     iconsAppearTranslationPx,
-                    statusViewMigrated,
                     animatorListener,
                 )
             }
             else -> {
                 // Let's make sure the icons are translated to 0, since we cancelled it above
-                animateInIconTranslation(statusViewMigrated)
+                animateInIconTranslation()
                 // We were fading out, let's fade in instead
                 CrossFadeHelper.fadeIn(this, animatorListener)
             }
@@ -445,11 +443,10 @@
     private fun View.appearIcons(
         animate: Boolean,
         iconAppearTranslation: Int,
-        statusViewMigrated: Boolean,
         animatorListener: Animator.AnimatorListener,
     ) {
         if (animate) {
-            if (!statusViewMigrated) {
+            if (!KeyguardShadeMigrationNssl.isEnabled) {
                 translationY = -iconAppearTranslation.toFloat()
             }
             alpha = 0f
@@ -457,19 +454,19 @@
                 .alpha(1f)
                 .setInterpolator(Interpolators.LINEAR)
                 .setDuration(AOD_ICONS_APPEAR_DURATION)
-                .apply { if (statusViewMigrated) animateInIconTranslation() }
+                .apply { if (KeyguardShadeMigrationNssl.isEnabled) animateInIconTranslation() }
                 .setListener(animatorListener)
                 .start()
         } else {
             alpha = 1.0f
-            if (!statusViewMigrated) {
+            if (!KeyguardShadeMigrationNssl.isEnabled) {
                 translationY = 0f
             }
         }
     }
 
-    private fun View.animateInIconTranslation(statusViewMigrated: Boolean) {
-        if (!statusViewMigrated) {
+    private fun View.animateInIconTranslation() {
+        if (!KeyguardShadeMigrationNssl.isEnabled) {
             animate().animateInIconTranslation().setDuration(AOD_ICONS_APPEAR_DURATION).start()
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
index 9d557bb..920fc04 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
@@ -15,6 +15,8 @@
  */
 package com.android.systemui.keyguard.ui.transitions
 
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToAodTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.AodToGoneTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel
@@ -39,6 +41,18 @@
 abstract class DeviceEntryIconTransitionModule {
     @Binds
     @IntoSet
+    abstract fun alternateBouncerToAod(
+        impl: AlternateBouncerToAodTransitionViewModel
+    ): DeviceEntryIconTransition
+
+    @Binds
+    @IntoSet
+    abstract fun alternateBouncerToGone(
+        impl: AlternateBouncerToGoneTransitionViewModel
+    ): DeviceEntryIconTransition
+
+    @Binds
+    @IntoSet
     abstract fun aodToLockscreen(
         impl: AodToLockscreenTransitionViewModel
     ): DeviceEntryIconTransition
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
index a693ec9..0bf9ad0 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
@@ -23,7 +23,6 @@
 import android.util.DisplayMetrics
 import android.view.View
 import android.view.WindowManager
-import android.widget.FrameLayout
 import androidx.annotation.VisibleForTesting
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
@@ -32,31 +31,24 @@
 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
 import com.android.systemui.keyguard.shared.model.KeyguardSection
-import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler
-import com.android.systemui.keyguard.ui.binder.AlternateBouncerViewBinder
 import com.android.systemui.keyguard.ui.binder.DeviceEntryIconViewBinder
 import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
-import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationPanelView
-import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.statusbar.gesture.TapGestureDetector
 import dagger.Lazy
 import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
-/** Includes both the device entry icon and the alternate bouncer scrim. */
+/** Includes the device entry icon. */
 @ExperimentalCoroutinesApi
 class DefaultDeviceEntrySection
 @Inject
@@ -72,27 +64,15 @@
     private val deviceEntryForegroundViewModel: Lazy<DeviceEntryForegroundViewModel>,
     private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>,
     private val falsingManager: Lazy<FalsingManager>,
-    private val alternateBouncerViewModel: Lazy<AlternateBouncerViewModel>,
-    private val notificationShadeWindowController: Lazy<NotificationShadeWindowController>,
-    @Application private val scope: CoroutineScope,
-    private val swipeUpAnywhereGestureHandler: Lazy<SwipeUpAnywhereGestureHandler>,
-    private val tapGestureDetector: Lazy<TapGestureDetector>,
     private val vibratorHelper: Lazy<VibratorHelper>,
 ) : KeyguardSection() {
     private val deviceEntryIconViewId = R.id.device_entry_icon_view
-    private val alternateBouncerViewId = R.id.alternate_bouncer
 
     override fun addViews(constraintLayout: ConstraintLayout) {
         if (!keyguardBottomAreaRefactor() && !DeviceEntryUdfpsRefactor.isEnabled) {
             return
         }
 
-        if (DeviceEntryUdfpsRefactor.isEnabled) {
-            // The alternate bouncer scrim needs to be below the device entry icon view, so
-            // we add the view here before adding the device entry icon view.
-            View.inflate(context, R.layout.alternate_bouncer, constraintLayout)
-        }
-
         notificationPanelView.findViewById<View>(R.id.lock_icon_view).let {
             notificationPanelView.removeView(it)
         }
@@ -119,17 +99,6 @@
                     vibratorHelper.get(),
                 )
             }
-            constraintLayout.findViewById<FrameLayout?>(alternateBouncerViewId)?.let {
-                AlternateBouncerViewBinder.bind(
-                    it,
-                    alternateBouncerViewModel.get(),
-                    scope,
-                    notificationShadeWindowController.get(),
-                    falsingManager.get(),
-                    swipeUpAnywhereGestureHandler.get(),
-                    tapGestureDetector.get(),
-                )
-            }
         } else {
             constraintLayout.findViewById<LockIconView?>(R.id.lock_icon_view)?.let {
                 lockIconViewController.get().setLockIconView(it)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt
new file mode 100644
index 0000000..a8e3be7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.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.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+
+/**
+ * Breaks down ALTERNATE BOUNCER->AOD transition into discrete steps for corresponding views to
+ * consume.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class AlternateBouncerToAodTransitionViewModel
+@Inject
+constructor(
+    interactor: KeyguardTransitionInteractor,
+    deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
+    animationFlow: KeyguardTransitionAnimationFlow,
+) : DeviceEntryIconTransition {
+    private val transitionAnimation =
+        animationFlow.setup(
+            duration = FromAlternateBouncerTransitionInteractor.TO_AOD_DURATION,
+            stepFlow = interactor.transition(KeyguardState.ALTERNATE_BOUNCER, KeyguardState.AOD),
+        )
+
+    val deviceEntryBackgroundViewAlpha: Flow<Float> =
+        transitionAnimation.sharedFlow(
+            duration = FromAlternateBouncerTransitionInteractor.TO_AOD_DURATION,
+            onStep = { 1 - it },
+            onFinish = { 0f },
+        )
+
+    override val deviceEntryParentViewAlpha: Flow<Float> =
+        deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfpsEnrolledAndEnabled
+            ->
+            if (udfpsEnrolledAndEnabled) {
+                transitionAnimation.immediatelyTransitionTo(1f)
+            } else {
+                emptyFlow()
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
index 023d16ca..5d6b0ce 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
@@ -18,8 +18,12 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor.Companion.TO_GONE_DURATION
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
 import com.android.systemui.keyguard.shared.model.ScrimAlpha
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
@@ -33,10 +37,20 @@
 class AlternateBouncerToGoneTransitionViewModel
 @Inject
 constructor(
+    interactor: KeyguardTransitionInteractor,
     bouncerToGoneFlows: BouncerToGoneFlows,
-) {
+    animationFlow: KeyguardTransitionAnimationFlow,
+) : DeviceEntryIconTransition {
+    private val transitionAnimation =
+        animationFlow.setup(
+            duration = TO_GONE_DURATION,
+            stepFlow = interactor.transition(ALTERNATE_BOUNCER, KeyguardState.GONE),
+        )
 
     /** Scrim alpha values */
     val scrimAlpha: Flow<ScrimAlpha> =
         bouncerToGoneFlows.scrimAlpha(TO_GONE_DURATION, ALTERNATE_BOUNCER)
+
+    override val deviceEntryParentViewAlpha: Flow<Float> =
+        transitionAnimation.immediatelyTransitionTo(0f)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
new file mode 100644
index 0000000..e18893a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.content.Context
+import android.hardware.biometrics.SensorLocationInternal
+import com.android.settingslib.Utils
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlin.math.roundToInt
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+/** Models the UI state for the UDFPS icon view in the alternate bouncer view. */
+@ExperimentalCoroutinesApi
+class AlternateBouncerUdfpsIconViewModel
+@Inject
+constructor(
+    val context: Context,
+    configurationInteractor: ConfigurationInteractor,
+    deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
+    fingerprintPropertyRepository: FingerprintPropertyRepository,
+) {
+    private val isSupported: Flow<Boolean> = deviceEntryUdfpsInteractor.isUdfpsSupported
+    val iconLocation: Flow<IconLocation> =
+        isSupported.flatMapLatest { supportsUI ->
+            if (supportsUI) {
+                fingerprintPropertyRepository.sensorLocations.map { sensorLocations ->
+                    val sensorLocation =
+                        sensorLocations.getOrDefault("", SensorLocationInternal.DEFAULT)
+                    IconLocation(
+                        left = sensorLocation.sensorLocationX - sensorLocation.sensorRadius,
+                        top = sensorLocation.sensorLocationY - sensorLocation.sensorRadius,
+                        right = sensorLocation.sensorLocationX + sensorLocation.sensorRadius,
+                        bottom = sensorLocation.sensorLocationY + sensorLocation.sensorRadius,
+                    )
+                }
+            } else {
+                emptyFlow()
+            }
+        }
+    val accessibilityDelegateHint: Flow<DeviceEntryIconView.AccessibilityHintType> =
+        flowOf(DeviceEntryIconView.AccessibilityHintType.ENTER)
+
+    private val fgIconColor: Flow<Int> =
+        configurationInteractor.onAnyConfigurationChange
+            .map { Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) }
+            .onStart {
+                emit(Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary))
+            }
+    private val fgIconPadding: Flow<Int> =
+        configurationInteractor.scaleForResolution.map { scale ->
+            (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
+                .roundToInt()
+        }
+    val fgViewModel: Flow<DeviceEntryForegroundViewModel.ForegroundIconViewModel> =
+        combine(
+            fgIconColor,
+            fgIconPadding,
+        ) { color, padding ->
+            DeviceEntryForegroundViewModel.ForegroundIconViewModel(
+                type = DeviceEntryIconView.IconType.FINGERPRINT,
+                useAodVariant = false,
+                tint = color,
+                padding = padding,
+            )
+        }
+
+    private val bgColor: Flow<Int> =
+        configurationInteractor.onAnyConfigurationChange
+            .map {
+                Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorSurface)
+            }
+            .onStart {
+                emit(
+                    Utils.getColorAttrDefaultColor(
+                        context,
+                        com.android.internal.R.attr.colorSurface
+                    )
+                )
+            }
+    val bgViewModel: Flow<DeviceEntryBackgroundViewModel.BackgroundViewModel> =
+        bgColor.map { color ->
+            DeviceEntryBackgroundViewModel.BackgroundViewModel(
+                alpha = 1f,
+                tint = color,
+            )
+        }
+
+    data class IconLocation(
+        val left: Int,
+        val top: Int,
+        val right: Int,
+        val bottom: Int,
+    ) {
+        val width = right - left
+        val height = bottom - top
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
index 3e8bbb3..c45caf0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
@@ -42,6 +42,7 @@
     occludedToAodTransitionViewModel: OccludedToAodTransitionViewModel,
     occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
     dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
+    alternateBouncerToAodTransitionViewModel: AlternateBouncerToAodTransitionViewModel,
 ) {
     private val color: Flow<Int> =
         configurationRepository.onAnyConfigurationChange
@@ -65,6 +66,7 @@
                 occludedToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
                 occludedToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
                 dreamingToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
+                alternateBouncerToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
             )
             .merge()
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
index 9a50d83..fe0b365 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
@@ -19,6 +19,7 @@
 
 import android.content.Context
 import com.android.settingslib.Utils
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.common.ui.data.repository.ConfigurationRepository
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -26,7 +27,6 @@
 import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
 import com.android.systemui.res.R
 import javax.inject.Inject
-import kotlin.math.roundToInt
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
@@ -45,6 +45,7 @@
     deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
     transitionInteractor: KeyguardTransitionInteractor,
     deviceEntryIconViewModel: DeviceEntryIconViewModel,
+    udfpsOverlayInteractor: UdfpsOverlayInteractor,
 ) {
     private val isShowingAod: Flow<Boolean> =
         transitionInteractor.startedKeyguardState.map { keyguardState ->
@@ -73,11 +74,7 @@
                 isTransitionToAod && isUdfps
             }
             .distinctUntilChanged()
-    private val padding: Flow<Int> =
-        configurationRepository.scaleForResolution.map { scale ->
-            (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
-                .roundToInt()
-        }
+    private val padding: Flow<Int> = udfpsOverlayInteractor.iconPadding
 
     val viewModel: Flow<ForegroundIconViewModel> =
         combine(
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 f95713b..eacaa40 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
@@ -29,6 +29,7 @@
 import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.util.kotlin.sample
 import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -52,15 +53,12 @@
     transitionInteractor: KeyguardTransitionInteractor,
     val keyguardInteractor: KeyguardInteractor,
     val viewModel: AodToLockscreenTransitionViewModel,
-    val shadeDependentFlows: ShadeDependentFlows,
     private val sceneContainerFlags: SceneContainerFlags,
     private val keyguardViewController: Lazy<KeyguardViewController>,
     private val deviceEntryInteractor: DeviceEntryInteractor,
 ) {
     private val intEvaluator = IntEvaluator()
     private val floatEvaluator = FloatEvaluator()
-    private val toAodFromState: Flow<KeyguardState> =
-        transitionInteractor.transitionStepsToState(KeyguardState.AOD).map { it.from }
     private val showingAlternateBouncer: Flow<Boolean> =
         transitionInteractor.startedKeyguardState.map { keyguardState ->
             keyguardState == KeyguardState.ALTERNATE_BOUNCER
@@ -95,13 +93,31 @@
                 fullyDozingBurnInProgress,
             )
         }
+
+    private val dozeAmount: Flow<Float> =
+        combine(
+            transitionInteractor.startedKeyguardTransitionStep,
+            merge(
+                transitionInteractor.transitionStepsFromState(KeyguardState.AOD).map {
+                    1f - it.value
+                },
+                transitionInteractor.transitionStepsToState(KeyguardState.AOD).map { it.value }
+            ),
+        ) { startedKeyguardTransitionStep, aodTransitionAmount ->
+            if (
+                startedKeyguardTransitionStep.to == KeyguardState.AOD ||
+                    startedKeyguardTransitionStep.from == KeyguardState.AOD
+            ) {
+                aodTransitionAmount
+            } else {
+                // in case a new transition (ie: to occluded) cancels a transition to or from
+                // aod, then we want to make sure the doze amount is still updated to 0
+                0f
+            }
+        }
     // Burn-in offsets that animate based on the transition amount to AOD
     private val animatedBurnInOffsets: Flow<BurnInOffsets> =
-        combine(
-            nonAnimatedBurnInOffsets,
-            transitionInteractor.transitionStepsToState(KeyguardState.AOD)
-        ) { burnInOffsets, transitionStepsToAod ->
-            val dozeAmount = transitionStepsToAod.value
+        combine(nonAnimatedBurnInOffsets, dozeAmount) { burnInOffsets, dozeAmount ->
             BurnInOffsets(
                 intEvaluator.evaluate(dozeAmount, 0, burnInOffsets.x),
                 intEvaluator.evaluate(dozeAmount, 0, burnInOffsets.y),
@@ -120,22 +136,35 @@
     val burnInOffsets: Flow<BurnInOffsets> =
         deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfpsEnrolled ->
             if (udfpsEnrolled) {
-                toAodFromState.flatMapLatest { fromState ->
-                    when (fromState) {
-                        KeyguardState.AOD,
-                        KeyguardState.GONE,
-                        KeyguardState.OCCLUDED,
-                        KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
-                        KeyguardState.OFF,
-                        KeyguardState.DOZING,
-                        KeyguardState.DREAMING,
-                        KeyguardState.PRIMARY_BOUNCER -> nonAnimatedBurnInOffsets
-                        KeyguardState.ALTERNATE_BOUNCER -> animatedBurnInOffsets
-                        KeyguardState.LOCKSCREEN ->
-                            shadeDependentFlows.transitionFlow(
-                                flowWhenShadeIsExpanded = nonAnimatedBurnInOffsets,
-                                flowWhenShadeIsNotExpanded = animatedBurnInOffsets,
-                            )
+                combine(
+                    transitionInteractor.startedKeyguardTransitionStep.sample(
+                        shadeInteractor.isAnyFullyExpanded,
+                        ::Pair
+                    ),
+                    animatedBurnInOffsets,
+                    nonAnimatedBurnInOffsets,
+                ) {
+                    (startedTransitionStep, shadeExpanded),
+                    animatedBurnInOffsets,
+                    nonAnimatedBurnInOffsets ->
+                    if (startedTransitionStep.to == KeyguardState.AOD) {
+                        when (startedTransitionStep.from) {
+                            KeyguardState.ALTERNATE_BOUNCER -> animatedBurnInOffsets
+                            KeyguardState.LOCKSCREEN ->
+                                if (shadeExpanded) {
+                                    nonAnimatedBurnInOffsets
+                                } else {
+                                    animatedBurnInOffsets
+                                }
+                            else -> nonAnimatedBurnInOffsets
+                        }
+                    } else if (startedTransitionStep.from == KeyguardState.AOD) {
+                        when (startedTransitionStep.to) {
+                            KeyguardState.LOCKSCREEN -> animatedBurnInOffsets
+                            else -> BurnInOffsets(x = 0, y = 0, progress = 0f)
+                        }
+                    } else {
+                        BurnInOffsets(x = 0, y = 0, progress = 0f)
                     }
                 }
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index a99c51c2..be93936 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -405,12 +405,15 @@
         }
         widgetGroupIds.forEach { id ->
             squishedViewState.widgetStates.get(id)?.let { state ->
-                state.alpha =
-                    calculateAlpha(
-                        squishFraction,
-                        startPosition / nonsquishedHeight,
-                        endPosition / nonsquishedHeight
-                    )
+                // Don't modify alpha for elements that should be invisible (e.g. disabled seekbar)
+                if (state.alpha != 0f) {
+                    state.alpha =
+                        calculateAlpha(
+                            squishFraction,
+                            startPosition / nonsquishedHeight,
+                            endPosition / nonsquishedHeight
+                        )
+                }
             }
         }
         return groupTop // used for the widget group above this group
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt
index 898298c..77279f2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.media.controls.util
 
 import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
 import com.android.systemui.flags.RefactorFlagUtils
 
 /** Helper for reading or using the media_in_scene_container flag state. */
@@ -25,6 +26,10 @@
     /** The aconfig flag name */
     const val FLAG_NAME = Flags.FLAG_MEDIA_IN_SCENE_CONTAINER
 
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
     /** Is the flag enabled? */
     @JvmStatic
     inline val isEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index 0e7e69b..270bfbe 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -263,11 +263,7 @@
      * If the shortcut entry `android:enabled` is set to `true`, the shortcut will be visible in the
      * Widget Picker to all users.
      */
-    // TODO(b/316332684)
-    @Suppress("UNREACHABLE_CODE")
     fun setNoteTaskShortcutEnabled(value: Boolean, user: UserHandle) {
-        return // shortcut should not be enabled until additional features are implemented.
-
         if (!userManager.isUserUnlocked(user)) {
             debugLog { "setNoteTaskShortcutEnabled call but user locked: user=$user" }
             return
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index 789a1e4..e08eb37 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -66,7 +66,8 @@
  */
 public class TileLifecycleManager extends BroadcastReceiver implements
         IQSTileService, ServiceConnection, IBinder.DeathRecipient {
-    public static final boolean DEBUG = false;
+
+    private final boolean mDebug = Log.isLoggable(TAG, Log.DEBUG);
 
     private static final String TAG = "TileLifecycleManager";
 
@@ -101,7 +102,7 @@
 
     private Set<Integer> mQueuedMessages = new ArraySet<>();
     @Nullable
-    private QSTileServiceWrapper mWrapper;
+    private volatile QSTileServiceWrapper mWrapper;
     private boolean mListening;
     private IBinder mClickBinder;
 
@@ -132,7 +133,7 @@
         mPackageManagerAdapter = packageManagerAdapter;
         mBroadcastDispatcher = broadcastDispatcher;
         mActivityManager = activityManager;
-        if (DEBUG) Log.d(TAG, "Creating " + mIntent + " " + mUser);
+        if (mDebug) Log.d(TAG, "Creating " + mIntent + " " + mUser);
     }
 
     /** Injectable factory for TileLifecycleManager. */
@@ -215,10 +216,11 @@
             if (!checkComponentState()) {
                 return;
             }
-            if (DEBUG) Log.d(TAG, "Binding service " + mIntent + " " + mUser);
+            if (mDebug) Log.d(TAG, "Binding service " + mIntent + " " + mUser);
             mBindTryCount++;
             try {
-                mIsBound.set(bindServices());
+                // Only try a new binding if we are not currently bound.
+                mIsBound.compareAndSet(false, bindServices());
                 if (!mIsBound.get()) {
                     mContext.unbindService(this);
                 }
@@ -227,19 +229,7 @@
                 mIsBound.set(false);
             }
         } else {
-            if (DEBUG) Log.d(TAG, "Unbinding service " + mIntent + " " + mUser);
-            // Give it another chance next time it needs to be bound, out of kindness.
-            mBindTryCount = 0;
-            freeWrapper();
-            if (mIsBound.get()) {
-                try {
-                    mContext.unbindService(this);
-                } catch (Exception e) {
-                    Log.e(TAG, "Failed to unbind service "
-                            + mIntent.getComponent().flattenToShortString(), e);
-                }
-                mIsBound.set(false);
-            }
+            unbindService();
         }
     }
 
@@ -264,9 +254,26 @@
                 mUser);
     }
 
+    @WorkerThread
+    private void unbindService() {
+        if (mDebug) Log.d(TAG, "Unbinding service " + mIntent + " " + mUser);
+        // Give it another chance next time it needs to be bound, out of kindness.
+        mBindTryCount = 0;
+        freeWrapper();
+        if (mIsBound.get()) {
+            try {
+                mContext.unbindService(this);
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to unbind service "
+                        + mIntent.getComponent().flattenToShortString(), e);
+            }
+            mIsBound.set(false);
+        }
+    }
+
     @Override
     public void onServiceConnected(ComponentName name, IBinder service) {
-        if (DEBUG) Log.d(TAG, "onServiceConnected " + name);
+        if (mDebug) Log.d(TAG, "onServiceConnected " + name);
         // Got a connection, set the binding count to 0.
         mBindTryCount = 0;
         final QSTileServiceWrapper wrapper = new QSTileServiceWrapper(Stub.asInterface(service));
@@ -284,11 +291,17 @@
     }
 
     @Override
-    public void onServiceDisconnected(ComponentName name) {
-        if (DEBUG) Log.d(TAG, "onServiceDisconnected " + name);
+    public void onBindingDied(ComponentName name) {
+        if (mDebug) Log.d(TAG, "onBindingDied " + name);
         handleDeath();
     }
 
+    @Override
+    public void onServiceDisconnected(ComponentName name) {
+        if (mDebug) Log.d(TAG, "onServiceDisconnected " + name);
+        freeWrapper();
+    }
+
     private void handlePendingMessages() {
         // This ordering is laid out manually to make sure we preserve the TileService
         // lifecycle.
@@ -298,35 +311,36 @@
             mQueuedMessages.clear();
         }
         if (queue.contains(MSG_ON_ADDED)) {
-            if (DEBUG) Log.d(TAG, "Handling pending onAdded");
+            if (mDebug) Log.d(TAG, "Handling pending onAdded " + getComponent());
             onTileAdded();
         }
         if (mListening) {
-            if (DEBUG) Log.d(TAG, "Handling pending onStartListening");
+            if (mDebug) Log.d(TAG, "Handling pending onStartListening " + getComponent());
             onStartListening();
         }
         if (queue.contains(MSG_ON_CLICK)) {
-            if (DEBUG) Log.d(TAG, "Handling pending onClick");
+            if (mDebug) Log.d(TAG, "Handling pending onClick " + getComponent());
             if (!mListening) {
-                Log.w(TAG, "Managed to get click on non-listening state...");
+                Log.w(TAG, "Managed to get click on non-listening state... " + getComponent());
                 // Skipping click since lost click privileges.
             } else {
                 onClick(mClickBinder);
             }
         }
         if (queue.contains(MSG_ON_UNLOCK_COMPLETE)) {
-            if (DEBUG) Log.d(TAG, "Handling pending onUnlockComplete");
+            if (mDebug) Log.d(TAG, "Handling pending onUnlockComplete " + getComponent());
             if (!mListening) {
-                Log.w(TAG, "Managed to get unlock on non-listening state...");
+                Log.w(TAG,
+                        "Managed to get unlock on non-listening state... " + getComponent());
                 // Skipping unlock since lost click privileges.
             } else {
                 onUnlockComplete();
             }
         }
         if (queue.contains(MSG_ON_REMOVED)) {
-            if (DEBUG) Log.d(TAG, "Handling pending onRemoved");
+            if (mDebug) Log.d(TAG, "Handling pending onRemoved " + getComponent());
             if (mListening) {
-                Log.w(TAG, "Managed to get remove in listening state...");
+                Log.w(TAG, "Managed to get remove in listening state... " + getComponent());
                 onStopListening();
             }
             onTileRemoved();
@@ -340,28 +354,44 @@
     }
 
     public void handleDestroy() {
-        if (DEBUG) Log.d(TAG, "handleDestroy");
+        if (mDebug) Log.d(TAG, "handleDestroy");
         if (mPackageReceiverRegistered.get() || mUserReceiverRegistered.get()) {
             stopPackageListening();
         }
         mChangeListener = null;
     }
 
+    /**
+     * Handles a dead binder.
+     *
+     * It means that we need to clean up the binding (calling unbindService). After that, if we
+     * are supposed to be bound, we will try to bind after some amount of time.
+     */
     private void handleDeath() {
-        if (mWrapper == null) return;
-        freeWrapper();
-        // Clearly not bound anymore
-        mIsBound.set(false);
-        if (!mBound.get()) return;
-        if (DEBUG) Log.d(TAG, "handleDeath");
-        if (checkComponentState()) {
-            if (isDeathRebindScheduled.compareAndSet(false, true)) {
-                mExecutor.executeDelayed(() -> {
-                    setBindService(true);
-                    isDeathRebindScheduled.set(false);
-                }, getRebindDelay());
+        mExecutor.execute(() -> {
+            if (!mIsBound.get()) {
+                // If we are already not bound, don't do anything else.
+                return;
             }
-        }
+            // Clearly we shouldn't be bound anymore
+            if (mDebug) Log.d(TAG, "handleDeath " + getComponent());
+            // Binder died, make sure that we unbind. However, we don't want to call setBindService
+            // as we still may want to rebind.
+            unbindService();
+            // If mBound is true (meaning that we should be bound), then reschedule binding for
+            // later.
+            if (mBound.get() && checkComponentState()) {
+                if (isDeathRebindScheduled.compareAndSet(false, true)) {
+                    mExecutor.executeDelayed(() -> {
+                        // Only rebind if we are supposed to, but remove the scheduling anyway.
+                        if (mBound.get()) {
+                            setBindService(true);
+                        }
+                        isDeathRebindScheduled.set(false);
+                    }, getRebindDelay());
+                }
+            }
+        });
     }
 
     /**
@@ -379,7 +409,7 @@
         } else {
             delay = mBindRetryDelay;
         }
-        Log.i(TAG, "Rebinding with a delay=" + delay);
+        if (mDebug) Log.i(TAG, "Rebinding with a delay=" + delay + " - " + getComponent());
         return delay;
     }
 
@@ -392,7 +422,7 @@
     }
 
     private void startPackageListening() {
-        if (DEBUG) Log.d(TAG, "startPackageListening");
+        if (mDebug) Log.d(TAG, "startPackageListening " + getComponent());
         IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
         filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
         filter.addDataScheme("package");
@@ -402,7 +432,7 @@
                     this, mUser, filter, null, mHandler, Context.RECEIVER_EXPORTED);
         } catch (Exception ex) {
             mPackageReceiverRegistered.set(false);
-            Log.e(TAG, "Could not register package receiver", ex);
+            Log.e(TAG, "Could not register package receiver " + getComponent(), ex);
         }
         filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
         try {
@@ -410,12 +440,12 @@
             mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mHandler, mUser);
         } catch (Exception ex) {
             mUserReceiverRegistered.set(false);
-            Log.e(TAG, "Could not register unlock receiver", ex);
+            Log.e(TAG, "Could not register unlock receiver " + getComponent(), ex);
         }
     }
 
     private void stopPackageListening() {
-        if (DEBUG) Log.d(TAG, "stopPackageListening");
+        if (mDebug) Log.d(TAG, "stopPackageListening " + getComponent());
         if (mUserReceiverRegistered.compareAndSet(true, false)) {
             mBroadcastDispatcher.unregisterReceiver(this);
         }
@@ -430,7 +460,7 @@
 
     @Override
     public void onReceive(Context context, Intent intent) {
-        if (DEBUG) Log.d(TAG, "onReceive: " + intent);
+        if (mDebug) Log.d(TAG, "onReceive: " + intent);
         if (!Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
             Uri data = intent.getData();
             String pkgName = data.getEncodedSchemeSpecificPart();
@@ -446,7 +476,7 @@
             if (mBound.get()) {
                 // Trying to bind again will check the state of the package before bothering to
                 // bind.
-                if (DEBUG) Log.d(TAG, "Trying to rebind");
+                if (mDebug) Log.d(TAG, "Trying to rebind " + getComponent());
                 setBindService(true);
             }
 
@@ -458,7 +488,9 @@
         try {
             ServiceInfo si = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(),
                     0, mUser.getIdentifier());
-            if (DEBUG && si == null) Log.d(TAG, "Can't find component " + mIntent.getComponent());
+            if (mDebug && si == null) {
+                Log.d(TAG, "Can't find component " + mIntent.getComponent());
+            }
             return si != null;
         } catch (RemoteException e) {
             // Shouldn't happen.
@@ -472,7 +504,7 @@
             mPackageManagerAdapter.getPackageInfoAsUser(packageName, 0, mUser.getIdentifier());
             return true;
         } catch (PackageManager.NameNotFoundException e) {
-            if (DEBUG) {
+            if (mDebug) {
                 Log.d(TAG, "Package not available: " + packageName, e);
             } else {
                 Log.d(TAG, "Package not available: " + packageName);
@@ -489,7 +521,7 @@
 
     @Override
     public void onTileAdded() {
-        if (DEBUG) Log.d(TAG, "onTileAdded");
+        if (mDebug) Log.d(TAG, "onTileAdded " + getComponent());
         if (mWrapper == null || !mWrapper.onTileAdded()) {
             queueMessage(MSG_ON_ADDED);
             handleDeath();
@@ -498,7 +530,7 @@
 
     @Override
     public void onTileRemoved() {
-        if (DEBUG) Log.d(TAG, "onTileRemoved");
+        if (mDebug) Log.d(TAG, "onTileRemoved " + getComponent());
         if (mWrapper == null || !mWrapper.onTileRemoved()) {
             queueMessage(MSG_ON_REMOVED);
             handleDeath();
@@ -507,7 +539,7 @@
 
     @Override
     public void onStartListening() {
-        if (DEBUG) Log.d(TAG, "onStartListening");
+        if (mDebug) Log.d(TAG, "onStartListening " + getComponent());
         mListening = true;
         if (mWrapper != null && !mWrapper.onStartListening()) {
             handleDeath();
@@ -516,7 +548,7 @@
 
     @Override
     public void onStopListening() {
-        if (DEBUG) Log.d(TAG, "onStopListening");
+        if (mDebug) Log.d(TAG, "onStopListening " + getComponent());
         mListening = false;
         if (mWrapper != null && !mWrapper.onStopListening()) {
             handleDeath();
@@ -525,7 +557,7 @@
 
     @Override
     public void onClick(IBinder iBinder) {
-        if (DEBUG) Log.d(TAG, "onClick " + iBinder + " " + mUser);
+        if (mDebug) Log.d(TAG, "onClick " + iBinder + " " + getComponent() + " " + mUser);
         if (mWrapper == null || !mWrapper.onClick(iBinder)) {
             mClickBinder = iBinder;
             queueMessage(MSG_ON_CLICK);
@@ -535,7 +567,7 @@
 
     @Override
     public void onUnlockComplete() {
-        if (DEBUG) Log.d(TAG, "onUnlockComplete");
+        if (mDebug) Log.d(TAG, "onUnlockComplete " + getComponent());
         if (mWrapper == null || !mWrapper.onUnlockComplete()) {
             queueMessage(MSG_ON_UNLOCK_COMPLETE);
             handleDeath();
@@ -550,7 +582,7 @@
 
     @Override
     public void binderDied() {
-        if (DEBUG) Log.d(TAG, "binderDeath");
+        if (mDebug) Log.d(TAG, "binderDeath " + getComponent());
         handleDeath();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt
index ca5302e..c932cee 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt
@@ -16,11 +16,15 @@
 
 package com.android.systemui.qs.tiles.impl.custom.data.repository
 
+import android.content.pm.PackageManager
+import android.content.pm.ServiceInfo
 import android.graphics.drawable.Icon
 import android.os.UserHandle
 import android.service.quicksettings.Tile
+import android.service.quicksettings.TileService
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.qs.external.CustomTileStatePersister
+import com.android.systemui.qs.external.PackageManagerAdapter
 import com.android.systemui.qs.external.TileServiceKey
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.tiles.impl.custom.commons.copy
@@ -63,6 +67,12 @@
      */
     fun getTile(user: UserHandle): Tile?
 
+    /** @see [com.android.systemui.qs.external.TileLifecycleManager.isActiveTile] */
+    suspend fun isTileActive(): Boolean
+
+    /** @see [com.android.systemui.qs.external.TileLifecycleManager.isToggleableTile] */
+    suspend fun isTileToggleable(): Boolean
+
     /**
      * Updates tile with the non-null values from [newTile]. Overwrites the current cache when
      * [user] differs from the cached one. [isPersistable] tile will be persisted to be possibly
@@ -92,6 +102,7 @@
 constructor(
     private val tileSpec: TileSpec.CustomTileSpec,
     private val customTileStatePersister: CustomTileStatePersister,
+    private val packageManagerAdapter: PackageManagerAdapter,
     @Background private val backgroundContext: CoroutineContext,
 ) : CustomTileRepository {
 
@@ -149,6 +160,34 @@
         }
     }
 
+    override suspend fun isTileActive(): Boolean =
+        withContext(backgroundContext) {
+            try {
+                val info: ServiceInfo =
+                    packageManagerAdapter.getServiceInfo(
+                        tileSpec.componentName,
+                        META_DATA_QUERY_FLAGS
+                    )
+                info.metaData?.getBoolean(TileService.META_DATA_ACTIVE_TILE, false) == true
+            } catch (e: PackageManager.NameNotFoundException) {
+                false
+            }
+        }
+
+    override suspend fun isTileToggleable(): Boolean =
+        withContext(backgroundContext) {
+            try {
+                val info: ServiceInfo =
+                    packageManagerAdapter.getServiceInfo(
+                        tileSpec.componentName,
+                        META_DATA_QUERY_FLAGS
+                    )
+                info.metaData?.getBoolean(TileService.META_DATA_TOGGLEABLE_TILE, false) == true
+            } catch (e: PackageManager.NameNotFoundException) {
+                false
+            }
+        }
+
     private suspend fun updateTile(
         user: UserHandle,
         isPersistable: Boolean,
@@ -193,4 +232,12 @@
     private fun UserHandle.getKey() = TileServiceKey(tileSpec.componentName, this.identifier)
 
     private data class TileWithUser(val user: UserHandle, val tile: Tile)
+
+    private companion object {
+        const val META_DATA_QUERY_FLAGS =
+            (PackageManager.GET_META_DATA or
+                PackageManager.MATCH_UNINSTALLED_PACKAGES or
+                PackageManager.MATCH_DIRECT_BOOT_UNAWARE or
+                PackageManager.MATCH_DIRECT_BOOT_AWARE)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt
index 351bba5..10b012d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt
@@ -19,10 +19,9 @@
 import android.os.UserHandle
 import android.service.quicksettings.Tile
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.qs.external.TileServiceManager
 import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepository
 import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileRepository
-import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileBoundScope
+import com.android.systemui.qs.tiles.impl.di.QSTileScope
 import javax.inject.Inject
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineScope
@@ -35,15 +34,13 @@
 import kotlinx.coroutines.flow.onEach
 
 /** Manages updates of the [Tile] assigned for the current custom tile. */
-@CustomTileBoundScope
+@QSTileScope
 class CustomTileInteractor
 @Inject
 constructor(
-    private val user: UserHandle,
     private val defaultsRepository: CustomTileDefaultsRepository,
     private val customTileRepository: CustomTileRepository,
-    private val tileServiceManager: TileServiceManager,
-    @CustomTileBoundScope private val boundScope: CoroutineScope,
+    @QSTileScope private val tileScope: CoroutineScope,
     @Background private val backgroundContext: CoroutineContext,
 ) {
 
@@ -51,8 +48,7 @@
         MutableSharedFlow<Tile>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
 
     /** [Tile] updates. [updateTile] to emit a new one. */
-    val tiles: Flow<Tile>
-        get() = customTileRepository.getTiles(user)
+    fun getTiles(user: UserHandle): Flow<Tile> = customTileRepository.getTiles(user)
 
     /**
      * Current [Tile]
@@ -61,10 +57,14 @@
      *   the tile hasn't been updated for the current user. Can happen when this is accessed before
      *   [init] returns.
      */
-    val tile: Tile
-        get() =
-            customTileRepository.getTile(user)
-                ?: throw IllegalStateException("Attempt to get a tile for a wrong user")
+    fun getTile(user: UserHandle): Tile =
+        customTileRepository.getTile(user)
+            ?: throw IllegalStateException("Attempt to get a tile for a wrong user")
+
+    /**
+     * True if the tile is toggleable like a switch and false if it operates as a clickable button.
+     */
+    suspend fun isTileToggleable(): Boolean = customTileRepository.isTileToggleable()
 
     /**
      * Initializes the repository for the current user. Suspends until it's safe to call [tile]
@@ -73,36 +73,36 @@
      * - receive tile update in [updateTile];
      * - restoration happened for a persisted tile.
      */
-    suspend fun init() {
-        launchUpdates()
-        customTileRepository.restoreForTheUserIfNeeded(user, tileServiceManager.isActiveTile)
+    suspend fun initForUser(user: UserHandle) {
+        launchUpdates(user)
+        customTileRepository.restoreForTheUserIfNeeded(user, customTileRepository.isTileActive())
         // Suspend to make sure it gets the tile from one of the sources: restoration, defaults, or
         // tile update.
         customTileRepository.getTiles(user).firstOrNull()
     }
 
-    private fun launchUpdates() {
+    private fun launchUpdates(user: UserHandle) {
         tileUpdates
             .onEach {
                 customTileRepository.updateWithTile(
                     user,
                     it,
-                    tileServiceManager.isActiveTile,
+                    customTileRepository.isTileActive(),
                 )
             }
             .flowOn(backgroundContext)
-            .launchIn(boundScope)
+            .launchIn(tileScope)
         defaultsRepository
             .defaults(user)
             .onEach {
                 customTileRepository.updateWithDefaults(
                     user,
                     it,
-                    tileServiceManager.isActiveTile,
+                    customTileRepository.isTileActive(),
                 )
             }
             .flowOn(backgroundContext)
-            .launchIn(boundScope)
+            .launchIn(tileScope)
     }
 
     /** Updates current [Tile]. Emits a new event in [tiles]. */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt
new file mode 100644
index 0000000..4af9854
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.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.qs.tiles.impl.inversion.domain
+
+import android.content.res.Resources
+import android.content.res.Resources.Theme
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.inversion.domain.model.ColorInversionTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [ColorInversionTileModel] to [QSTileState]. */
+class ColorInversionTileMapper
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val theme: Theme,
+) : QSTileDataToStateMapper<ColorInversionTileModel> {
+    override fun map(config: QSTileConfig, data: ColorInversionTileModel): QSTileState =
+        QSTileState.build(resources, theme, config.uiConfig) {
+            val subtitleArray = resources.getStringArray(R.array.tile_states_inversion)
+
+            if (data.isEnabled) {
+                activationState = QSTileState.ActivationState.ACTIVE
+                secondaryLabel = subtitleArray[2]
+                icon = {
+                    Icon.Loaded(
+                        resources.getDrawable(R.drawable.qs_invert_colors_icon_on, theme),
+                        null
+                    )
+                }
+            } else {
+                activationState = QSTileState.ActivationState.INACTIVE
+                secondaryLabel = subtitleArray[1]
+                icon = {
+                    Icon.Loaded(
+                        resources.getDrawable(R.drawable.qs_invert_colors_icon_off, theme),
+                        null
+                    )
+                }
+            }
+            contentDescription = label
+            supportedActions =
+                setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionTileDataInteractor.kt
new file mode 100644
index 0000000..7f3dd3e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionTileDataInteractor.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.tiles.impl.inversion.domain.interactor
+
+import android.os.UserHandle
+import com.android.systemui.accessibility.data.repository.ColorInversionRepository
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.inversion.domain.model.ColorInversionTileModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+/** Observes color inversion state changes providing the [ColorInversionTileModel]. */
+class ColorInversionTileDataInteractor
+@Inject
+constructor(
+    private val colorInversionRepository: ColorInversionRepository,
+) : QSTileDataInteractor<ColorInversionTileModel> {
+
+    override fun tileData(
+        user: UserHandle,
+        triggers: Flow<DataUpdateTrigger>
+    ): Flow<ColorInversionTileModel> {
+        return colorInversionRepository.isEnabled(user).map { ColorInversionTileModel(it) }
+    }
+    override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionUserActionInteractor.kt
new file mode 100644
index 0000000..43b58c8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionUserActionInteractor.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.qs.tiles.impl.inversion.domain.interactor
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.systemui.accessibility.data.repository.ColorInversionRepository
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.inversion.domain.model.ColorInversionTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import javax.inject.Inject
+
+/** Handles color inversion tile clicks. */
+class ColorInversionUserActionInteractor
+@Inject
+constructor(
+    private val colorInversionRepository: ColorInversionRepository,
+    private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+) : QSTileUserActionInteractor<ColorInversionTileModel> {
+
+    override suspend fun handleInput(input: QSTileInput<ColorInversionTileModel>): Unit =
+        with(input) {
+            when (action) {
+                is QSTileUserAction.Click -> {
+                    colorInversionRepository.setIsEnabled(
+                        !data.isEnabled,
+                        user,
+                    )
+                }
+                is QSTileUserAction.LongClick -> {
+                    qsTileIntentUserActionHandler.handle(
+                        action.view,
+                        Intent(Settings.ACTION_COLOR_INVERSION_SETTINGS)
+                    )
+                }
+            }
+        }
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/model/ColorInversionTileModel.kt
similarity index 72%
copy from core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl
copy to packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/model/ColorInversionTileModel.kt
index 6c1f0fc..affaa6d 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/model/ColorInversionTileModel.kt
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package android.companion.virtual.camera;
+package com.android.systemui.qs.tiles.impl.inversion.domain.model
 
 /**
- * Data structure used to store {@link android.hardware.camera2.CameraMetadata} compatible with
- * VirtualCamera.
- * @hide
+ * Color inversion tile model.
+ *
+ * @param isEnabled is true when the color inversion is enabled;
  */
-parcelable VirtualCameraMetadata;
+@JvmInline value class ColorInversionTileModel(val isEnabled: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
index 580c421..ef3df48 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
@@ -22,8 +22,8 @@
 
 /**
  * Represents tiles behaviour logic. This ViewModel is a connection between tile view and data
- * layers. All direct inheritors must be added to the [QSTileViewModelInterfaceComplianceTest] class
- * to pass compliance tests.
+ * layers. All direct inheritors must be added to the
+ * [com.android.systemui.qs.tiles.viewmodel.QSTileViewModelTest] class to pass interface tests.
  *
  * All methods of this view model should be considered running on the main thread. This means no
  * synchronous long running operations are permitted in any method.
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
index 1156250..8c3e4a5 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
@@ -14,30 +14,94 @@
  * limitations under the License.
  */
 
+@file:Suppress("NOTHING_TO_INLINE")
+
 package com.android.systemui.scene.shared.flag
 
-import android.content.Context
-import androidx.annotation.VisibleForTesting
-import com.android.systemui.Flags as AConfigFlags
+import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
+import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
 import com.android.systemui.Flags.keyguardBottomAreaRefactor
 import com.android.systemui.Flags.sceneContainer
 import com.android.systemui.compose.ComposeFacade
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flag
-import com.android.systemui.flags.Flags
-import com.android.systemui.flags.ReleasedFlag
-import com.android.systemui.flags.ResourceBooleanFlag
-import com.android.systemui.flags.UnreleasedFlag
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.Flags.SCENE_CONTAINER_ENABLED
+import com.android.systemui.flags.RefactorFlagUtils
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.media.controls.util.MediaInSceneContainerFlag
-import com.android.systemui.res.R
 import dagger.Module
 import dagger.Provides
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
+
+/** Helper for reading or using the scene container flag state. */
+object SceneContainerFlag {
+    /** The flag description -- not an aconfig flag name */
+    const val DESCRIPTION = "SceneContainerFlag"
+
+    inline val isEnabled
+        get() =
+            SCENE_CONTAINER_ENABLED && // mainStaticFlag
+            sceneContainer() && // mainAconfigFlag
+                keyguardBottomAreaRefactor() &&
+                KeyguardShadeMigrationNssl.isEnabled &&
+                MediaInSceneContainerFlag.isEnabled &&
+                ComposeFacade.isComposeAvailable()
+
+    /**
+     * The main static flag, SCENE_CONTAINER_ENABLED. This is an explicit static flag check that
+     * helps with downstream optimizations (like unused code stripping) in builds where aconfig
+     * flags are still writable. Do not remove!
+     */
+    inline fun getMainStaticFlag() =
+        FlagToken("Flags.SCENE_CONTAINER_ENABLED", SCENE_CONTAINER_ENABLED)
+
+    /** The main aconfig flag. */
+    inline fun getMainAconfigFlag() = FlagToken(FLAG_SCENE_CONTAINER, sceneContainer())
+
+    /** The set of secondary flags which must be enabled for scene container to work properly */
+    inline fun getSecondaryFlags(): Sequence<FlagToken> =
+        sequenceOf(
+            FlagToken(FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor()),
+            KeyguardShadeMigrationNssl.token,
+            MediaInSceneContainerFlag.token,
+        )
+
+    /** The full set of requirements for SceneContainer */
+    inline fun getAllRequirements(): Sequence<FlagToken> {
+        val composeRequirement =
+            FlagToken("ComposeFacade.isComposeAvailable()", ComposeFacade.isComposeAvailable())
+        return sequenceOf(getMainStaticFlag(), getMainAconfigFlag()) +
+            getSecondaryFlags() +
+            composeRequirement
+    }
+
+    /** Return all dependencies of this flag in pairs where [Pair.first] depends on [Pair.second] */
+    inline fun getFlagDependencies(): Sequence<Pair<FlagToken, FlagToken>> {
+        val mainStaticFlag = getMainStaticFlag()
+        val mainAconfigFlag = getMainAconfigFlag()
+        return sequence {
+            // The static and aconfig flags should be equal; make them co-dependent
+            yield(mainAconfigFlag to mainStaticFlag)
+            yield(mainStaticFlag to mainAconfigFlag)
+            // all other flags depend on the static flag for brevity
+        } + getSecondaryFlags().map { mainStaticFlag to it }
+    }
+
+    /**
+     * 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, DESCRIPTION)
+
+    /**
+     * 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, DESCRIPTION)
+}
 
 /**
  * Defines interface for classes that can check whether the scene container framework feature is
@@ -52,133 +116,25 @@
     fun requirementDescription(): String
 }
 
-class SceneContainerFlagsImpl
-@AssistedInject
-constructor(
-    @Application private val context: Context,
-    private val featureFlagsClassic: FeatureFlagsClassic,
-    @Assisted private val isComposeAvailable: Boolean,
-) : SceneContainerFlags {
-
-    companion object {
-        @VisibleForTesting
-        val classicFlagTokens: List<Flag<Boolean>> =
-            listOf(
-                Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW,
-            )
-    }
-
-    /** The list of requirements, all must be met for the feature to be enabled. */
-    private val requirements =
-        listOf(
-            AconfigFlagMustBeEnabled(
-                flagName = AConfigFlags.FLAG_SCENE_CONTAINER,
-                flagValue = sceneContainer(),
-            ),
-            AconfigFlagMustBeEnabled(
-                flagName = AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
-                flagValue = keyguardBottomAreaRefactor(),
-            ),
-            AconfigFlagMustBeEnabled(
-                flagName = KeyguardShadeMigrationNssl.FLAG_NAME,
-                flagValue = KeyguardShadeMigrationNssl.isEnabled,
-            ),
-            AconfigFlagMustBeEnabled(
-                flagName = MediaInSceneContainerFlag.FLAG_NAME,
-                flagValue = MediaInSceneContainerFlag.isEnabled,
-            ),
-        ) +
-            classicFlagTokens.map { flagToken -> FlagMustBeEnabled(flagToken) } +
-            listOf(
-                ComposeMustBeAvailable(),
-                CompileTimeFlagMustBeEnabled(),
-                ResourceConfigMustBeEnabled()
-            )
+class SceneContainerFlagsImpl : SceneContainerFlags {
 
     override fun isEnabled(): Boolean {
-        // SCENE_CONTAINER_ENABLED is an explicit static flag check that helps with downstream
-        // optimizations, e.g., unused code stripping. Do not remove!
-        return Flags.SCENE_CONTAINER_ENABLED && requirements.all { it.isMet() }
+        return SceneContainerFlag.isEnabled
     }
 
     override fun requirementDescription(): String {
         return buildString {
-            requirements.forEach { requirement ->
+            SceneContainerFlag.getAllRequirements().forEach { requirement ->
                 append('\n')
-                append(if (requirement.isMet()) "    [MET]" else "[NOT MET]")
+                append(if (requirement.isEnabled) "    [MET]" else "[NOT MET]")
                 append(" ${requirement.name}")
             }
         }
     }
-
-    private interface Requirement {
-        val name: String
-
-        fun isMet(): Boolean
-    }
-
-    private inner class ComposeMustBeAvailable : Requirement {
-        override val name = "Jetpack Compose must be available"
-
-        override fun isMet(): Boolean {
-            return isComposeAvailable
-        }
-    }
-
-    private inner class CompileTimeFlagMustBeEnabled : Requirement {
-        override val name = "Flags.SCENE_CONTAINER_ENABLED must be enabled in code"
-
-        override fun isMet(): Boolean {
-            return Flags.SCENE_CONTAINER_ENABLED
-        }
-    }
-
-    private inner class FlagMustBeEnabled<FlagType : Flag<*>>(
-        private val flag: FlagType,
-    ) : Requirement {
-        override val name = "Flag ${flag.name} must be enabled"
-
-        override fun isMet(): Boolean {
-            return when (flag) {
-                is ResourceBooleanFlag -> featureFlagsClassic.isEnabled(flag)
-                is ReleasedFlag -> featureFlagsClassic.isEnabled(flag)
-                is UnreleasedFlag -> featureFlagsClassic.isEnabled(flag)
-                else -> error("Unsupported flag type ${flag.javaClass}")
-            }
-        }
-    }
-
-    private inner class AconfigFlagMustBeEnabled(
-        flagName: String,
-        private val flagValue: Boolean,
-    ) : Requirement {
-        override val name: String = "Aconfig flag $flagName must be enabled"
-
-        override fun isMet(): Boolean {
-            return flagValue
-        }
-    }
-
-    private inner class ResourceConfigMustBeEnabled : Requirement {
-        override val name: String = "R.bool.config_sceneContainerFrameworkEnabled must be true"
-
-        override fun isMet(): Boolean {
-            return context.resources.getBoolean(R.bool.config_sceneContainerFrameworkEnabled)
-        }
-    }
-
-    @AssistedFactory
-    interface Factory {
-        fun create(isComposeAvailable: Boolean): SceneContainerFlagsImpl
-    }
 }
 
 @Module
 object SceneContainerFlagsModule {
 
-    @Provides
-    @SysUISingleton
-    fun impl(factory: SceneContainerFlagsImpl.Factory): SceneContainerFlags {
-        return factory.create(ComposeFacade.isComposeAvailable())
-    }
+    @Provides @SysUISingleton fun impl(): SceneContainerFlags = SceneContainerFlagsImpl()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
new file mode 100644
index 0000000..ab69acb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -0,0 +1,186 @@
+/*
+ * 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.shade
+
+import android.content.Context
+import android.view.GestureDetector
+import android.view.MotionEvent
+import android.view.View
+import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.compose.ComposeFacade.createCommunalContainer
+import com.android.systemui.compose.ComposeFacade.isComposeAvailable
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.util.kotlin.collectFlow
+import javax.inject.Inject
+
+/**
+ * Controller that's responsible for the glanceable hub container view and its touch handling.
+ *
+ * This will be used until the glanceable hub is integrated into Flexiglass.
+ */
+class GlanceableHubContainerController
+@Inject
+constructor(
+    private val communalInteractor: CommunalInteractor,
+    private val communalViewModel: CommunalViewModel,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    private val shadeInteractor: ShadeInteractor,
+) {
+    /** The container view for the hub. This will not be initialized until [initView] is called. */
+    private lateinit var communalContainerView: View
+
+    /**
+     * The width of the area in which a right edge swipe can open the hub, in pixels. Read from
+     * resources when [initView] is called.
+     */
+    private var edgeSwipeRegionWidth: Int = 0
+
+    /**
+     * True if we are currently tracking a gesture for opening the hub that started in the edge
+     * swipe region.
+     */
+    private var isTrackingOpenGesture = false
+
+    /**
+     * True if the hub UI is fully open, meaning it should receive touch input.
+     *
+     * Tracks [CommunalInteractor.isCommunalShowing].
+     */
+    private var hubShowing = false
+
+    /**
+     * True if either the primary or alternate bouncer are open, meaning the hub should not receive
+     * any touch input.
+     *
+     * Tracks [KeyguardTransitionInteractor.isFinishedInState] for [KeyguardState.isBouncerState].
+     */
+    private var anyBouncerShowing = false
+
+    /**
+     * True if the shade is fully expanded, meaning the hub should not receive any touch input.
+     *
+     * Tracks [ShadeInteractor.isAnyFullyExpanded].
+     */
+    private var shadeShowing = false
+
+    /** Returns true if the glanceable hub is enabled and the container view can be created. */
+    fun isEnabled(): Boolean {
+        return communalInteractor.isCommunalEnabled && isComposeAvailable()
+    }
+
+    /**
+     * Creates the container view containing the glanceable hub UI.
+     *
+     * @throws RuntimeException if [isEnabled] is false or the view is already initialized
+     */
+    fun initView(
+        context: Context,
+    ): View {
+        return initView(createCommunalContainer(context, communalViewModel))
+    }
+
+    /** Override for testing. */
+    @VisibleForTesting
+    internal fun initView(containerView: View): View {
+        if (!isEnabled()) {
+            throw RuntimeException("Glanceable hub is not enabled")
+        }
+        if (::communalContainerView.isInitialized) {
+            throw RuntimeException("Communal view has already been initialized")
+        }
+
+        communalContainerView = containerView
+
+        edgeSwipeRegionWidth =
+            communalContainerView.resources.getDimensionPixelSize(R.dimen.communal_grid_gutter_size)
+
+        collectFlow(
+            communalContainerView,
+            keyguardTransitionInteractor.isFinishedInStateWhere(KeyguardState::isBouncerState),
+            { anyBouncerShowing = it }
+        )
+        collectFlow(
+            communalContainerView,
+            communalInteractor.isCommunalShowing,
+            { hubShowing = it }
+        )
+        collectFlow(
+            communalContainerView,
+            shadeInteractor.isAnyFullyExpanded,
+            { shadeShowing = it }
+        )
+
+        return communalContainerView
+    }
+
+    /**
+     * Notifies the hub container of a touch event. Returns true if it's determined that the touch
+     * should go to the hub container and no one else.
+     *
+     * Special handling is needed because the hub container sits at the lowest z-order in
+     * [NotificationShadeWindowView] and would not normally receive touches. We also cannot use a
+     * [GestureDetector] as the hub container's SceneTransitionLayout is a Compose view that expects
+     * to be fully in control of its own touch handling.
+     */
+    fun onTouchEvent(ev: MotionEvent): Boolean {
+        if (!::communalContainerView.isInitialized) {
+            return false
+        }
+
+        val isDown = ev.actionMasked == MotionEvent.ACTION_DOWN
+        val isUp = ev.actionMasked == MotionEvent.ACTION_UP
+        val isCancel = ev.actionMasked == MotionEvent.ACTION_CANCEL
+
+        // TODO(b/315207481): also account for opening animations of shade/bouncer and not just
+        //  fully showing state
+        val hubOccluded = anyBouncerShowing || shadeShowing
+
+        // If the hub is fully visible, send all touch events to it.
+        val communalVisible = hubShowing && !hubOccluded
+        if (communalVisible) {
+            return communalContainerView.dispatchTouchEvent(ev)
+        }
+
+        if (edgeSwipeRegionWidth == 0) {
+            // If the edge region width has not been read yet or whatever reason, don't bother
+            // intercepting touches to open the hub.
+            return false
+        }
+
+        if (!isTrackingOpenGesture && isDown) {
+            val x = ev.rawX
+            val inOpeningSwipeRegion: Boolean =
+                x >= communalContainerView.width - edgeSwipeRegionWidth
+            if (inOpeningSwipeRegion && !hubOccluded) {
+                isTrackingOpenGesture = true
+                return communalContainerView.dispatchTouchEvent(ev)
+            }
+        } else if (isTrackingOpenGesture) {
+            if (isUp || isCancel) {
+                isTrackingOpenGesture = false
+            }
+            return communalContainerView.dispatchTouchEvent(ev)
+        }
+
+        return false
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 95f7c94a..878e6fa 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1096,7 +1096,7 @@
             }
 
             @Override
-            public void onPulseExpansionChanged(boolean expandingChanged) {
+            public void onPulseExpansionAmountChanged(boolean expandingChanged) {
                 if (mKeyguardBypassController.getBypassEnabled()) {
                     // Position the notifications while dragging down while pulsing
                     requestScrollerTopPaddingUpdate(false /* animate */);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 73537ed..3cf468f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -42,9 +42,6 @@
 import com.android.systemui.bouncer.ui.binder.KeyguardBouncerViewBinder;
 import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel;
 import com.android.systemui.classifier.FalsingCollector;
-import com.android.systemui.communal.data.repository.CommunalRepository;
-import com.android.systemui.communal.ui.viewmodel.CommunalViewModel;
-import com.android.systemui.compose.ComposeFacade;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor;
 import com.android.systemui.dock.DockManager;
@@ -57,8 +54,13 @@
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
 import com.android.systemui.keyguard.shared.model.TransitionState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
+import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler;
+import com.android.systemui.keyguard.ui.binder.AlternateBouncerViewBinder;
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
 import com.android.systemui.log.BouncerLogger;
+import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.res.R;
 import com.android.systemui.shared.animation.DisableSubpixelTextTransitionListener;
 import com.android.systemui.statusbar.DragDownHelper;
@@ -67,6 +69,7 @@
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.gesture.TapGestureDetector;
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
@@ -79,14 +82,19 @@
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.time.SystemClock;
 
+import dagger.Lazy;
+
 import java.io.PrintWriter;
 import java.util.Optional;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
 
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
+
 /**
  * Controller for {@link NotificationShadeWindowView}.
  */
@@ -108,14 +116,14 @@
     private final PulsingGestureListener mPulsingGestureListener;
     private final LockscreenHostedDreamGestureListener mLockscreenHostedDreamGestureListener;
     private final NotificationInsetsController mNotificationInsetsController;
-    private final CommunalViewModel mCommunalViewModel;
-    private final CommunalRepository mCommunalRepository;
     private final boolean mIsTrackpadCommonEnabled;
     private final FeatureFlagsClassic mFeatureFlagsClassic;
     private final SysUIKeyEventHandler mSysUIKeyEventHandler;
     private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
     private final AlternateBouncerInteractor mAlternateBouncerInteractor;
     private final QuickSettingsController mQuickSettingsController;
+    private final GlanceableHubContainerController
+            mGlanceableHubContainerController;
     private GestureDetector mPulsingWakeupGestureHandler;
     private GestureDetector mDreamingWakeupGestureHandler;
     private View mBrightnessMirror;
@@ -152,6 +160,7 @@
             };
     private final SystemClock mClock;
 
+    @ExperimentalCoroutinesApi
     @Inject
     public NotificationShadeWindowViewController(
             LockscreenShadeTransitionController transitionController,
@@ -183,8 +192,7 @@
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
             PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
-            CommunalViewModel communalViewModel,
-            CommunalRepository communalRepository,
+            GlanceableHubContainerController glanceableHubContainerController,
             NotificationLaunchAnimationInteractor notificationLaunchAnimationInteractor,
             FeatureFlagsClassic featureFlagsClassic,
             SystemClock clock,
@@ -194,7 +202,13 @@
             QuickSettingsController quickSettingsController,
             PrimaryBouncerInteractor primaryBouncerInteractor,
             AlternateBouncerInteractor alternateBouncerInteractor,
-            SelectedUserInteractor selectedUserInteractor) {
+            SelectedUserInteractor selectedUserInteractor,
+            Lazy<JavaAdapter> javaAdapter,
+            Lazy<AlternateBouncerViewModel> alternateBouncerViewModel,
+            Lazy<FalsingManager> falsingManager,
+            Lazy<SwipeUpAnywhereGestureHandler> swipeUpAnywhereGestureHandler,
+            Lazy<TapGestureDetector> tapGestureDetector,
+            Lazy<AlternateBouncerUdfpsIconViewModel> alternateBouncerUdfpsIconViewModel) {
         mLockscreenShadeTransitionController = transitionController;
         mFalsingCollector = falsingCollector;
         mStatusBarStateController = statusBarStateController;
@@ -217,8 +231,7 @@
         mPulsingGestureListener = pulsingGestureListener;
         mLockscreenHostedDreamGestureListener = lockscreenHostedDreamGestureListener;
         mNotificationInsetsController = notificationInsetsController;
-        mCommunalViewModel = communalViewModel;
-        mCommunalRepository = communalRepository;
+        mGlanceableHubContainerController = glanceableHubContainerController;
         mIsTrackpadCommonEnabled = featureFlagsClassic.isEnabled(TRACKPAD_GESTURE_COMMON);
         mFeatureFlagsClassic = featureFlagsClassic;
         mSysUIKeyEventHandler = sysUIKeyEventHandler;
@@ -240,6 +253,25 @@
                 featureFlagsClassic,
                 selectedUserInteractor);
 
+        if (DeviceEntryUdfpsRefactor.isEnabled()) {
+            AlternateBouncerViewBinder.bind(
+                    mView.findViewById(R.id.alternate_bouncer),
+                    alternateBouncerViewModel.get(),
+                    falsingManager.get(),
+                    swipeUpAnywhereGestureHandler.get(),
+                    tapGestureDetector.get(),
+                    alternateBouncerUdfpsIconViewModel.get()
+            );
+            javaAdapter.get().alwaysCollectFlow(
+                    alternateBouncerViewModel.get().getForcePluginOpen(),
+                    forcePluginOpen ->
+                            mNotificationShadeWindowController.setForcePluginOpen(
+                                    forcePluginOpen,
+                                    alternateBouncerViewModel.get()
+                            )
+            );
+        }
+
         collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(),
                 mLockscreenToDreamingTransition);
         collectFlow(
@@ -347,6 +379,10 @@
 
                 mFalsingCollector.onTouchEvent(ev);
                 mPulsingWakeupGestureHandler.onTouchEvent(ev);
+
+                if (mGlanceableHubContainerController.onTouchEvent(ev)) {
+                    return logDownDispatch(ev, "dispatched to glanceable hub container", true);
+                }
                 if (mDreamingWakeupGestureHandler != null
                         && mDreamingWakeupGestureHandler.onTouchEvent(ev)) {
                     return logDownDispatch(ev, "dream wakeup gesture handled", true);
@@ -587,14 +623,13 @@
     }
 
     /**
-     * Sets up the communal hub UI if the {@link com.android.systemui.Flags#FLAG_COMMUNAL_HUB} flag
-     * is enabled.
+     * Sets up the glanceable hub UI if the {@link com.android.systemui.Flags#FLAG_COMMUNAL_HUB}
+     * flag is enabled.
      *
-     * The layout lives in {@link R.id.communal_ui_container}.
+     * The layout lives in {@link R.id.communal_ui_stub}.
      */
     public void setupCommunalHubLayout() {
-        if (!mCommunalRepository.isCommunalEnabled()
-                || !ComposeFacade.INSTANCE.isComposeAvailable()) {
+        if (!mGlanceableHubContainerController.isEnabled()) {
             return;
         }
 
@@ -602,8 +637,8 @@
         View communalPlaceholder = mView.findViewById(R.id.communal_ui_stub);
         int index = mView.indexOfChild(communalPlaceholder);
         mView.removeView(communalPlaceholder);
-        mView.addView(ComposeFacade.INSTANCE.createCommunalContainer(mView.getContext(),
-                mCommunalViewModel), index);
+
+        mView.addView(mGlanceableHubContainerController.initView(mView.getContext()), index);
     }
 
     private boolean didNotificationPanelInterceptEvent(MotionEvent ev) {
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
index d26fded1..2f58b35 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
@@ -41,14 +41,14 @@
         const val DREAM_WEATHER_SMARTSPACE_DATA_PLUGIN = "dream_weather_smartspace_data_plugin"
 
         /**
-         * The dream smartspace target filter.
+         * The target filter for smartspace over lockscreen.
          */
-        const val DREAM_SMARTSPACE_TARGET_FILTER = "dream_smartspace_target_filter"
+        const val LOCKSCREEN_SMARTSPACE_TARGET_FILTER = "lockscreen_smartspace_target_filter"
 
         /**
-         * The precondition for dream smartspace
+         * The precondition for smartspace over lockscreen
          */
-        const val DREAM_SMARTSPACE_PRECONDITION = "dream_smartspace_precondition"
+        const val LOCKSCREEN_SMARTSPACE_PRECONDITION = "lockscreen_smartspace_precondition"
 
         /**
          * The BcSmartspaceDataPlugin for the standalone date (+alarm+dnd).
@@ -67,7 +67,7 @@
     }
 
     @BindsOptionalOf
-    @Named(DREAM_SMARTSPACE_TARGET_FILTER)
+    @Named(LOCKSCREEN_SMARTSPACE_TARGET_FILTER)
     abstract fun optionalDreamSmartspaceTargetFilter(): SmartspaceTargetFilter?
 
     @BindsOptionalOf
@@ -79,7 +79,7 @@
     abstract fun optionalDreamWeatherSmartspaceDataPlugin(): BcSmartspaceDataPlugin?
 
     @Binds
-    @Named(DREAM_SMARTSPACE_PRECONDITION)
+    @Named(LOCKSCREEN_SMARTSPACE_PRECONDITION)
     abstract fun bindSmartspacePrecondition(
         lockscreenPrecondition: LockscreenPrecondition?
     ): SmartspacePrecondition?
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/filters/LockscreenAndDreamTargetFilter.kt b/packages/SystemUI/src/com/android/systemui/smartspace/filters/LockscreenTargetFilter.kt
similarity index 80%
rename from packages/SystemUI/src/com/android/systemui/smartspace/filters/LockscreenAndDreamTargetFilter.kt
rename to packages/SystemUI/src/com/android/systemui/smartspace/filters/LockscreenTargetFilter.kt
index aa4cf75..5fb85e5 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/filters/LockscreenAndDreamTargetFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/filters/LockscreenTargetFilter.kt
@@ -31,10 +31,10 @@
 import java.util.concurrent.Executor
 import javax.inject.Inject
 
-/**
- * {@link SmartspaceTargetFilter} for smartspace targets that show above the lockscreen and dreams.
- */
-class LockscreenAndDreamTargetFilter @Inject constructor(
+/** {@link SmartspaceTargetFilter} for smartspace targets that show above the lockscreen. */
+class LockscreenTargetFilter
+@Inject
+constructor(
     private val secureSettings: SecureSettings,
     private val userTracker: UserTracker,
     private val execution: Execution,
@@ -60,12 +60,13 @@
             }
         }
 
-    private val settingsObserver = object : ContentObserver(handler) {
-        override fun onChange(selfChange: Boolean, uri: Uri?) {
-            execution.assertIsMainThread()
-            updateUserContentSettings()
+    private val settingsObserver =
+        object : ContentObserver(handler) {
+            override fun onChange(selfChange: Boolean, uri: Uri?) {
+                execution.assertIsMainThread()
+                updateUserContentSettings()
+            }
         }
-    }
 
     private var managedUserHandle: UserHandle? = null
 
@@ -79,10 +80,10 @@
         userTracker.addCallback(userTrackerCallback, uiExecutor)
 
         contentResolver.registerContentObserver(
-                secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
-                true,
-                settingsObserver,
-                UserHandle.USER_ALL
+            secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
+            true,
+            settingsObserver,
+            UserHandle.USER_ALL
         )
 
         updateUserContentSettings()
@@ -110,7 +111,7 @@
                 // Only the primary user can have an associated managed profile, so only show
                 // content for the managed profile if the primary user is active
                 userTracker.userHandle.identifier == UserHandle.USER_SYSTEM &&
-                        (!t.isSensitive || showSensitiveContentForManagedUser)
+                    (!t.isSensitive || showSensitiveContentForManagedUser)
             }
             else -> {
                 false
@@ -118,12 +119,13 @@
         }
     }
 
-    private val userTrackerCallback = object : UserTracker.Callback {
-        override fun onUserChanged(newUser: Int, userContext: Context) {
-            execution.assertIsMainThread()
-            updateUserContentSettings()
+    private val userTrackerCallback =
+        object : UserTracker.Callback {
+            override fun onUserChanged(newUser: Int, userContext: Context) {
+                execution.assertIsMainThread()
+                updateUserContentSettings()
+            }
         }
-    }
 
     private fun getWorkProfileUser(): UserHandle? {
         for (userInfo in userTracker.userProfiles) {
@@ -138,13 +140,13 @@
         val setting = Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS
 
         showSensitiveContentForCurrentUser =
-                secureSettings.getIntForUser(setting, 0, userTracker.userId) == 1
+            secureSettings.getIntForUser(setting, 0, userTracker.userId) == 1
 
         managedUserHandle = getWorkProfileUser()
         val managedId = managedUserHandle?.identifier
         if (managedId != null) {
             showSensitiveContentForManagedUser =
-                    secureSettings.getIntForUser(setting, 0, managedId) == 1
+                secureSettings.getIntForUser(setting, 0, managedId) == 1
         }
 
         listeners.forEach { it.onCriteriaChanged() }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 843a454..984fcad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -27,7 +27,6 @@
 import android.app.Notification;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -59,6 +58,7 @@
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.util.ContrastColorUtil;
 import com.android.systemui.res.R;
+import com.android.systemui.statusbar.notification.NotificationContentDescription;
 import com.android.systemui.statusbar.notification.NotificationDozeHelper;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
@@ -350,9 +350,22 @@
     }
 
     public void setNotification(StatusBarNotification notification) {
-        mNotification = notification;
+        CharSequence contentDescription = null;
         if (notification != null) {
-            setContentDescription(notification.getNotification());
+            contentDescription = NotificationContentDescription
+                    .contentDescForNotification(mContext, notification.getNotification());
+        }
+        setNotification(notification, contentDescription);
+    }
+
+    /**
+     * Sets the notification with a pre-set content description.
+     */
+    public void setNotification(@Nullable StatusBarNotification notification,
+            @Nullable CharSequence notificationContentDescription) {
+        mNotification = notification;
+        if (!TextUtils.isEmpty(notificationContentDescription)) {
+            setContentDescription(notificationContentDescription);
         }
         maybeUpdateIconScaleDimens();
     }
@@ -621,15 +634,6 @@
         mNumberBackground.setBounds(w-dw, h-dh, w, h);
     }
 
-    private void setContentDescription(Notification notification) {
-        if (notification != null) {
-            String d = contentDescForNotification(mContext, notification);
-            if (!TextUtils.isEmpty(d)) {
-                setContentDescription(d);
-            }
-        }
-    }
-
     @Override
     public String toString() {
         return "StatusBarIconView("
@@ -647,35 +651,6 @@
         return mSlot;
     }
 
-
-    public static String contentDescForNotification(Context c, Notification n) {
-        String appName = "";
-        try {
-            Notification.Builder builder = Notification.Builder.recoverBuilder(c, n);
-            appName = builder.loadHeaderAppName();
-        } catch (RuntimeException e) {
-            Log.e(TAG, "Unable to recover builder", e);
-            // Trying to get the app name from the app info instead.
-            ApplicationInfo appInfo = n.extras.getParcelable(
-                    Notification.EXTRA_BUILDER_APPLICATION_INFO, ApplicationInfo.class);
-            if (appInfo != null) {
-                appName = String.valueOf(appInfo.loadLabel(c.getPackageManager()));
-            }
-        }
-
-        CharSequence title = n.extras.getCharSequence(Notification.EXTRA_TITLE);
-        CharSequence text = n.extras.getCharSequence(Notification.EXTRA_TEXT);
-        CharSequence ticker = n.tickerText;
-
-        // Some apps just put the app name into the title
-        CharSequence titleOrText = TextUtils.equals(title, appName) ? text : title;
-
-        CharSequence desc = !TextUtils.isEmpty(titleOrText) ? titleOrText
-                : !TextUtils.isEmpty(ticker) ? ticker : "";
-
-        return c.getString(R.string.accessibility_desc_notification_icon, appName, desc);
-    }
-
     /**
      * Set the color that is used to draw decoration like the overflow dot. This will not be applied
      * to the drawable.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationContentDescription.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationContentDescription.kt
new file mode 100644
index 0000000..e7012ea51
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationContentDescription.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+@file:JvmName("NotificationContentDescription")
+
+package com.android.systemui.statusbar.notification
+
+import android.app.Notification
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.text.TextUtils
+import android.util.Log
+import androidx.annotation.MainThread
+import com.android.systemui.res.R
+
+/**
+ * Returns accessibility content description for a given notification.
+ *
+ * NOTE: This is a relatively slow call.
+ */
+@MainThread
+fun contentDescForNotification(c: Context, n: Notification?): CharSequence {
+    var appName = ""
+    try {
+        val builder = Notification.Builder.recoverBuilder(c, n)
+        appName = builder.loadHeaderAppName()
+    } catch (e: RuntimeException) {
+        Log.e("ContentDescription", "Unable to recover builder", e)
+        // Trying to get the app name from the app info instead.
+        val appInfo =
+            n?.extras?.getParcelable(
+                Notification.EXTRA_BUILDER_APPLICATION_INFO,
+                ApplicationInfo::class.java
+            )
+        if (appInfo != null) {
+            appName = appInfo.loadLabel(c.packageManager).toString()
+        }
+    }
+    val title = n?.extras?.getCharSequence(Notification.EXTRA_TITLE)
+    val text = n?.extras?.getCharSequence(Notification.EXTRA_TEXT)
+    val ticker = n?.tickerText
+
+    // Some apps just put the app name into the title
+    val titleOrText = if (TextUtils.equals(title, appName)) text else title
+    val desc =
+        if (!TextUtils.isEmpty(titleOrText)) titleOrText
+        else if (!TextUtils.isEmpty(ticker)) ticker else ""
+    return c.getString(R.string.accessibility_desc_notification_icon, appName, desc)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 8d1e8d0..0c67279 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -20,9 +20,9 @@
 import android.view.animation.Interpolator
 import androidx.annotation.VisibleForTesting
 import androidx.core.animation.ObjectAnimator
-import com.android.systemui.Dumpable
 import com.android.app.animation.Interpolators
 import com.android.app.animation.InterpolatorsAndroidX
+import com.android.systemui.Dumpable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -31,6 +31,7 @@
 import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
 import com.android.systemui.statusbar.phone.DozeParameters
@@ -206,8 +207,15 @@
             val nowExpanding = isPulseExpanding()
             val changed = nowExpanding != pulseExpanding
             pulseExpanding = nowExpanding
-            for (listener in wakeUpListeners) {
-                listener.onPulseExpansionChanged(changed)
+            if (!NotificationIconContainerRefactor.isEnabled) {
+                for (listener in wakeUpListeners) {
+                    listener.onPulseExpansionAmountChanged(changed)
+                }
+            }
+            if (changed) {
+                for (listener in wakeUpListeners) {
+                    listener.onPulseExpandingChanged(pulseExpanding)
+                }
             }
         }
     }
@@ -620,13 +628,20 @@
          *
          * @param expandingChanged if the user has started or stopped expanding
          */
-        fun onPulseExpansionChanged(expandingChanged: Boolean) {}
+        @Deprecated(
+            message = "Use onPulseExpandedChanged instead.",
+            replaceWith = ReplaceWith("onPulseExpandedChanged"),
+        )
+        fun onPulseExpansionAmountChanged(expandingChanged: Boolean) {}
 
         /**
          * Called when the animator started by [scheduleDelayedDozeAmountAnimation] begins running
          * after the start delay, or after it ends/is cancelled.
          */
         fun onDelayedDozeAmountAnimationRunning(running: Boolean) {}
+
+        /** Called whenever a pulse has started or stopped expanding. */
+        fun onPulseExpandingChanged(isPulseExpanding: Boolean) {}
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt
index cf03d1c..2cc1403 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt
@@ -62,8 +62,8 @@
     override val isPulseExpanding: Flow<Boolean> = conflatedCallbackFlow {
         val listener =
             object : NotificationWakeUpCoordinator.WakeUpListener {
-                override fun onPulseExpansionChanged(expandingChanged: Boolean) {
-                    trySend(expandingChanged)
+                override fun onPulseExpandingChanged(isPulseExpanding: Boolean) {
+                    trySend(isPulseExpanding)
                 }
             }
         trySend(wakeUpCoordinator.isPulseExpanding())
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
index afc123f..319b499 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
@@ -20,6 +20,7 @@
 import android.content.Context
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.contentDescForNotification
 import javax.inject.Inject
 
 /**
@@ -36,6 +37,6 @@
     }
 
     fun getIconContentDescription(n: Notification): CharSequence {
-        return StatusBarIconView.contentDescForNotification(context, n)
+        return contentDescForNotification(context, n)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index 9e8654a..76e5fd3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -26,15 +26,15 @@
 import android.util.Log
 import android.view.View
 import android.widget.ImageView
+import com.android.app.tracing.traceSection
 import com.android.internal.statusbar.StatusBarIcon
-import com.android.systemui.res.R
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.notification.InflationException
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
-import com.android.app.tracing.traceSection
 import javax.inject.Inject
 
 /**
@@ -112,15 +112,6 @@
         aodIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
         aodIcon.setIncreasedSize(true)
 
-        // Construct the centered icon view.
-        val centeredIcon = if (entry.sbn.notification.isMediaNotification) {
-            iconBuilder.createIconView(entry).apply {
-                scaleType = ImageView.ScaleType.CENTER_INSIDE
-            }
-        } else {
-            null
-        }
-
         // Set the icon views' icons
         val (normalIconDescriptor, sensitiveIconDescriptor) = getIconDescriptors(entry)
 
@@ -128,10 +119,7 @@
             setIcon(entry, normalIconDescriptor, sbIcon)
             setIcon(entry, sensitiveIconDescriptor, shelfIcon)
             setIcon(entry, sensitiveIconDescriptor, aodIcon)
-            if (centeredIcon != null) {
-                setIcon(entry, normalIconDescriptor, centeredIcon)
-            }
-            entry.icons = IconPack.buildPack(sbIcon, shelfIcon, aodIcon, centeredIcon, entry.icons)
+            entry.icons = IconPack.buildPack(sbIcon, shelfIcon, aodIcon, entry.icons)
         } catch (e: InflationException) {
             entry.icons = IconPack.buildEmptyPack(entry.icons)
             throw e
@@ -153,24 +141,22 @@
         entry.icons.peopleAvatarDescriptor = null
 
         val (normalIconDescriptor, sensitiveIconDescriptor) = getIconDescriptors(entry)
+        val notificationContentDescription = entry.sbn.notification?.let {
+            iconBuilder.getIconContentDescription(it)
+        }
 
         entry.icons.statusBarIcon?.let {
-            it.notification = entry.sbn
+            it.setNotification(entry.sbn, notificationContentDescription)
             setIcon(entry, normalIconDescriptor, it)
         }
 
         entry.icons.shelfIcon?.let {
-            it.notification = entry.sbn
+            it.setNotification(entry.sbn, notificationContentDescription)
             setIcon(entry, normalIconDescriptor, it)
         }
 
         entry.icons.aodIcon?.let {
-            it.notification = entry.sbn
-            setIcon(entry, sensitiveIconDescriptor, it)
-        }
-
-        entry.icons.centeredIcon?.let {
-            it.notification = entry.sbn
+            it.setNotification(entry.sbn, notificationContentDescription)
             setIcon(entry, sensitiveIconDescriptor, it)
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
index 054e381..442c097 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
@@ -31,7 +31,6 @@
     @Nullable private final StatusBarIconView mStatusBarIcon;
     @Nullable private final StatusBarIconView mShelfIcon;
     @Nullable private final StatusBarIconView mAodIcon;
-    @Nullable private final StatusBarIconView mCenteredIcon;
 
     @Nullable private StatusBarIcon mSmallIconDescriptor;
     @Nullable private StatusBarIcon mPeopleAvatarDescriptor;
@@ -43,7 +42,7 @@
      * haven't been inflated yet or there was an error while inflating them).
      */
     public static IconPack buildEmptyPack(@Nullable IconPack fromSource) {
-        return new IconPack(false, null, null, null, null, fromSource);
+        return new IconPack(false, null, null, null, fromSource);
     }
 
     /**
@@ -53,9 +52,8 @@
             @NonNull StatusBarIconView statusBarIcon,
             @NonNull StatusBarIconView shelfIcon,
             @NonNull StatusBarIconView aodIcon,
-            @Nullable StatusBarIconView centeredIcon,
             @Nullable IconPack source) {
-        return new IconPack(true, statusBarIcon, shelfIcon, aodIcon, centeredIcon, source);
+        return new IconPack(true, statusBarIcon, shelfIcon, aodIcon, source);
     }
 
     private IconPack(
@@ -63,12 +61,10 @@
             @Nullable StatusBarIconView statusBarIcon,
             @Nullable StatusBarIconView shelfIcon,
             @Nullable StatusBarIconView aodIcon,
-            @Nullable StatusBarIconView centeredIcon,
             @Nullable IconPack source) {
         mAreIconsAvailable = areIconsAvailable;
         mStatusBarIcon = statusBarIcon;
         mShelfIcon = shelfIcon;
-        mCenteredIcon = centeredIcon;
         mAodIcon = aodIcon;
         if (source != null) {
             mIsImportantConversation = source.mIsImportantConversation;
@@ -91,11 +87,6 @@
         return mShelfIcon;
     }
 
-    @Nullable
-    public StatusBarIconView getCenteredIcon() {
-        return mCenteredIcon;
-    }
-
     /** The version of the icon that's shown when pulsing (in AOD). */
     @Nullable
     public StatusBarIconView getAodIcon() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionRefactor.kt
index 2624363..db33f92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionRefactor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionRefactor.kt
@@ -17,12 +17,17 @@
 package com.android.systemui.statusbar.notification.interruption
 
 import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
 import com.android.systemui.flags.RefactorFlagUtils
 
 /** Helper for reading or using the visual interruptions refactor flag state. */
 object VisualInterruptionRefactor {
     const val FLAG_NAME = Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR
 
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
     /** Whether the refactor is enabled */
     @JvmStatic
     inline val isEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index b6d4ded..5872840 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -38,6 +38,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.SystemProperties;
 import android.os.Trace;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
@@ -272,6 +273,13 @@
     private final RefactorFlag mInlineReplyAnimation =
             RefactorFlag.forView(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION);
 
+    private static final boolean mSimulateSlowMeasure = Compile.IS_DEBUG && RefactorFlag.forView(
+            Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE).isEnabled();
+    private static final String SLOW_MEASURE_SIMULATE_DELAY_PROPERTY =
+            "persist.notifications.extra_measure_delay_ms";
+    private static final int SLOW_MEASURE_SIMULATE_DELAY_MS = mSimulateSlowMeasure ?
+            SystemProperties.getInt(SLOW_MEASURE_SIMULATE_DELAY_PROPERTY, 150) : 0;
+
     // Listener will be called when receiving a long click event.
     // Use #setLongPressPosition to optionally assign positional data with the long press.
     private LongPressListener mLongPressListener;
@@ -1879,9 +1887,26 @@
                     + "heightMeasureSpec=" + MeasureSpec.toString(heightMeasureSpec) + ")");
         }
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        if (Compile.IS_DEBUG && mSimulateSlowMeasure) {
+            simulateExtraMeasureDelay();
+        }
         Trace.endSection();
     }
 
+    private void simulateExtraMeasureDelay() {
+        // Add extra delay in a notification row instead of NotificationStackScrollLayout
+        // to make sure that when the measure cache is used we won't add this delay
+        try {
+            Trace.beginSection("ExtraDebugMeasureDelay");
+            Thread.sleep(SLOW_MEASURE_SIMULATE_DELAY_MS);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        } finally {
+            Trace.endSection();
+        }
+    }
+
     /**
      * Generates and appends "(MessagingStyle)" type tag to passed string for tracing.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/AsyncHybridViewInflation.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/AsyncHybridViewInflation.kt
index 24e7f05..9556c2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/AsyncHybridViewInflation.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/AsyncHybridViewInflation.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.row.shared
 
 import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
 import com.android.systemui.flags.RefactorFlagUtils
 
 /** Helper for reading or using the async hybrid view inflation flag state. */
@@ -24,6 +25,10 @@
 object AsyncHybridViewInflation {
     const val FLAG_NAME = Flags.FLAG_NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION
 
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
     /** Is async hybrid (single-line) view inflation enabled */
     @JvmStatic
     inline val isEnabled
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 3bbdfd1..85e63e5 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
@@ -94,9 +94,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.res.R;
-import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.TouchLogger;
-import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.StatusBarState;
@@ -109,7 +107,6 @@
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
-import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -146,8 +143,6 @@
     private static final String TAG = "StackScroller";
     private static final boolean SPEW = Log.isLoggable(TAG, Log.VERBOSE);
 
-    // Delay in milli-seconds before shade closes for clear all.
-    private static final int DELAY_BEFORE_SHADE_CLOSE = 200;
     private boolean mShadeNeedsToClose = false;
 
     @VisibleForTesting
@@ -319,7 +314,7 @@
         }
     };
     private NotificationStackScrollLogger mLogger;
-    private NotificationsController mNotificationsController;
+    private Runnable mResetUserExpandedStatesRunnable;
     private ActivityStarter mActivityStarter;
     private final int[] mTempInt2 = new int[2];
     private final HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
@@ -482,7 +477,7 @@
     private final Rect mTmpRect = new Rect();
     private ClearAllListener mClearAllListener;
     private ClearAllAnimationListener mClearAllAnimationListener;
-    private ShadeController mShadeController;
+    private Runnable mClearAllFinishedWhilePanelExpandedRunnable;
     private Consumer<Boolean> mOnStackYChanged;
 
     private Interpolator mHideXInterpolator = Interpolators.FAST_OUT_SLOW_IN;
@@ -4114,7 +4109,7 @@
         mAmbientState.setExpansionChanging(false);
         if (!mIsExpanded) {
             resetScrollPosition();
-            mNotificationsController.resetUserExpandedStates();
+            mResetUserExpandedStatesRunnable.run();
             clearTemporaryViews();
             clearUserLockedViews();
             resetAllSwipeState();
@@ -4316,21 +4311,12 @@
             if (mShadeNeedsToClose) {
                 mShadeNeedsToClose = false;
                 if (mIsExpanded) {
-                    collapseShadeDelayed();
+                    mClearAllFinishedWhilePanelExpandedRunnable.run();
                 }
             }
         }
     }
 
-    private void collapseShadeDelayed() {
-        postDelayed(
-                () -> {
-                    mShadeController.animateCollapseShade(
-                            CommandQueue.FLAG_EXCLUDE_NONE);
-                },
-                DELAY_BEFORE_SHADE_CLOSE /* delayMillis */);
-    }
-
     private void clearHeadsUpDisappearRunning() {
         for (int i = 0; i < getChildCount(); i++) {
             View view = getChildAt(i);
@@ -4747,8 +4733,8 @@
         return max + getStackTranslation();
     }
 
-    public void setNotificationsController(NotificationsController notificationsController) {
-        this.mNotificationsController = notificationsController;
+    public void setResetUserExpandedStatesRunnable(Runnable runnable) {
+        this.mResetUserExpandedStatesRunnable = runnable;
     }
 
     public void setActivityStarter(ActivityStarter activityStarter) {
@@ -5681,8 +5667,8 @@
         mFooterClearAllListener = listener;
     }
 
-    void setShadeController(ShadeController shadeController) {
-        mShadeController = shadeController;
+    void setClearAllFinishedWhilePanelExpandedRunnable(Runnable runnable) {
+        mClearAllFinishedWhilePanelExpandedRunnable = runnable;
     }
 
     /**
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 e6315fd..d2fca8f 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
@@ -81,6 +81,7 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
@@ -152,6 +153,8 @@
     private static final String TAG = "StackScrollerController";
     private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
     private static final String HIGH_PRIORITY = "high_priority";
+    /** Delay in milli-seconds before shade closes for clear all. */
+    private static final int DELAY_BEFORE_SHADE_CLOSE = 200;
 
     private final boolean mAllowLongPress;
     private final NotificationGutsManager mNotificationGutsManager;
@@ -754,7 +757,7 @@
         mView.setController(this);
         mView.setLogger(mLogger);
         mView.setTouchHandler(new TouchHandler());
-        mView.setNotificationsController(mNotificationsController);
+        mView.setResetUserExpandedStatesRunnable(mNotificationsController::resetUserExpandedStates);
         mView.setActivityStarter(mActivityStarter);
         mView.setClearAllAnimationListener(this::onAnimationEnd);
         mView.setClearAllListener((selection) -> mUiEventLogger.log(
@@ -770,7 +773,11 @@
                 mView.setIsRemoteInputActive(active);
             }
         });
-        mView.setShadeController(mShadeController);
+        mView.setClearAllFinishedWhilePanelExpandedRunnable(()-> {
+            final Runnable doCollapseRunnable = () ->
+                    mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
+            mView.postDelayed(doCollapseRunnable, /* delayMillis = */ DELAY_BEFORE_SHADE_CLOSE);
+        });
         mDumpManager.registerDumpable(mView);
 
         mKeyguardBypassController.registerOnBypassStateChangedListener(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractor.kt
index 4de3a7f..3a650aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractor.kt
@@ -18,7 +18,7 @@
 import android.graphics.Rect
 import android.util.Log
 import com.android.app.tracing.FlowTracing.traceEach
-import com.android.systemui.common.domain.interactor.ConfigurationInteractor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.power.shared.model.ScreenPowerState.SCREEN_ON
@@ -28,6 +28,7 @@
 import com.android.systemui.util.kotlin.area
 import com.android.systemui.util.kotlin.pairwise
 import com.android.systemui.util.kotlin.race
+import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.TimeoutCancellationException
 import kotlinx.coroutines.flow.Flow
@@ -38,7 +39,6 @@
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.withTimeout
-import javax.inject.Inject
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/DisplaySwitchNotificationsHiderFlag.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/DisplaySwitchNotificationsHiderFlag.kt
index 98c1734..3cd9a8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/DisplaySwitchNotificationsHiderFlag.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/DisplaySwitchNotificationsHiderFlag.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.stack.shared
 
 import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
 import com.android.systemui.flags.RefactorFlagUtils
 
 /** Helper for reading or using the DisplaySwitchNotificationsHider flag state. */
@@ -24,6 +25,10 @@
 object DisplaySwitchNotificationsHiderFlag {
     const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_HIDE_ON_DISPLAY_SWITCH
 
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
     /** Is the hiding enabled? */
     @JvmStatic
     inline val isEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
index a62a1ed..e79f3ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
@@ -608,7 +608,7 @@
     }
 
     @Override
-    public void onPulseExpansionChanged(boolean expandingChanged) {
+    public void onPulseExpansionAmountChanged(boolean expandingChanged) {
         if (expandingChanged) {
             updateAodIconsVisibility(true /* animate */, false /* force */);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 0dabafb..f34a44a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -284,11 +284,22 @@
 
     @Override
     public String toString() {
-        return "NotificationIconContainer("
-                + "dozing=" + mDozing + " onLockScreen=" + mOnLockScreen
-                + " overrideIconColor=" + mOverrideIconColor
-                + " speedBumpIndex=" + mSpeedBumpIndex
-                + " themedTextColorPrimary=#" + Integer.toHexString(mThemedTextColorPrimary) + ')';
+        if (NotificationIconContainerRefactor.isEnabled()) {
+            return super.toString()
+                    + " {"
+                    + " overrideIconColor=" + mOverrideIconColor
+                    + ", maxIcons=" + mMaxIcons
+                    + ", isStaticLayout=" + mIsStaticLayout
+                    + ", themedTextColorPrimary=#" + Integer.toHexString(mThemedTextColorPrimary)
+                    + " }";
+        } else {
+            return "NotificationIconContainer("
+                    + "dozing=" + mDozing + " onLockScreen=" + mOnLockScreen
+                    + " overrideIconColor=" + mOverrideIconColor
+                    + " speedBumpIndex=" + mSpeedBumpIndex
+                    + " themedTextColorPrimary=#" + Integer.toHexString(mThemedTextColorPrimary)
+                    + ')';
+        }
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index a14e87c..5475528 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -30,6 +30,7 @@
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.settingslib.graph.SignalDrawable
+import com.android.systemui.Flags.statusBarStaticInoutIndicators
 import com.android.systemui.common.ui.binder.ContentDescriptionViewBinder
 import com.android.systemui.common.ui.binder.IconViewBinder
 import com.android.systemui.lifecycle.repeatWhenAttached
@@ -41,6 +42,8 @@
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
 import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
 import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewVisibilityHelper
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarViewBinderConstants.ALPHA_ACTIVE
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarViewBinderConstants.ALPHA_INACTIVE
 import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
@@ -190,10 +193,29 @@
                         }
                     }
 
-                    // Set the activity indicators
-                    launch { viewModel.activityInVisible.collect { activityIn.isVisible = it } }
+                    if (statusBarStaticInoutIndicators()) {
+                        // Set the opacity of the activity indicators
+                        launch {
+                            viewModel.activityInVisible.collect { visible ->
+                                activityIn.imageAlpha =
+                                    (if (visible) ALPHA_ACTIVE else ALPHA_INACTIVE)
+                            }
+                        }
 
-                    launch { viewModel.activityOutVisible.collect { activityOut.isVisible = it } }
+                        launch {
+                            viewModel.activityOutVisible.collect { visible ->
+                                activityOut.imageAlpha =
+                                    (if (visible) ALPHA_ACTIVE else ALPHA_INACTIVE)
+                            }
+                        }
+                    } else {
+                        // Set the activity indicators
+                        launch { viewModel.activityInVisible.collect { activityIn.isVisible = it } }
+
+                        launch {
+                            viewModel.activityOutVisible.collect { activityOut.isVisible = it }
+                        }
+                    }
 
                     launch {
                         viewModel.activityContainerVisible.collect {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index d88c9ef..60c662d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
 
 import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
+import com.android.systemui.Flags.statusBarStaticInoutIndicators
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.flags.FeatureFlagsClassic
@@ -199,7 +200,10 @@
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val activityContainerVisible: Flow<Boolean> =
-        activity
-            .map { it != null && (it.hasActivityIn || it.hasActivityOut) }
+        if (statusBarStaticInoutIndicators()) {
+                flowOf(constants.shouldShowActivityConfig)
+            } else {
+                activity.map { it != null && (it.hasActivityIn || it.hasActivityOut) }
+            }
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 }
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/StatusBarViewBinderConstants.kt
similarity index 65%
copy from core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl
copy to packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/StatusBarViewBinderConstants.kt
index 6c1f0fc..7348d5f 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/StatusBarViewBinderConstants.kt
@@ -13,12 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package com.android.systemui.statusbar.pipeline.shared.ui.binder
 
-package android.companion.virtual.camera;
-
-/**
- * Data structure used to store {@link android.hardware.camera2.CameraMetadata} compatible with
- * VirtualCamera.
- * @hide
- */
-parcelable VirtualCameraMetadata;
+object StatusBarViewBinderConstants {
+    /** Opacity for the traffic indicator icons */
+    const val ALPHA_ACTIVE: Int = (1.0 * 255).toInt() // Fully opaque
+    const val ALPHA_INACTIVE: Int = (0.3 * 255).toInt() // 30% opaque -> 70% transparency
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index 6005527..9512490 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -23,6 +23,7 @@
 import androidx.core.view.isVisible
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.Flags.statusBarStaticInoutIndicators
 import com.android.systemui.common.ui.binder.IconViewBinder
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.res.R
@@ -30,6 +31,8 @@
 import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
 import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
 import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewVisibilityHelper
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarViewBinderConstants.ALPHA_ACTIVE
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarViewBinderConstants.ALPHA_INACTIVE
 import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
 import kotlinx.coroutines.InternalCoroutinesApi
@@ -118,15 +121,36 @@
 
                 launch { decorTint.collect { tint -> dotView.setDecorColor(tint) } }
 
-                launch {
-                    viewModel.isActivityInViewVisible.distinctUntilChanged().collect { visible ->
-                        activityInView.isVisible = visible
+                if (statusBarStaticInoutIndicators()) {
+                    // Set the opacity of the activity indicators
+                    launch {
+                        viewModel.isActivityInViewVisible.distinctUntilChanged().collect { visible
+                            ->
+                            activityInView.imageAlpha =
+                                (if (visible) ALPHA_ACTIVE else ALPHA_INACTIVE)
+                        }
                     }
-                }
 
-                launch {
-                    viewModel.isActivityOutViewVisible.distinctUntilChanged().collect { visible ->
-                        activityOutView.isVisible = visible
+                    launch {
+                        viewModel.isActivityOutViewVisible.distinctUntilChanged().collect { visible
+                            ->
+                            activityOutView.imageAlpha =
+                                (if (visible) ALPHA_ACTIVE else ALPHA_INACTIVE)
+                        }
+                    }
+                } else {
+                    launch {
+                        viewModel.isActivityInViewVisible.distinctUntilChanged().collect { visible
+                            ->
+                            activityInView.isVisible = visible
+                        }
+                    }
+
+                    launch {
+                        viewModel.isActivityOutViewVisible.distinctUntilChanged().collect { visible
+                            ->
+                            activityOutView.isVisible = visible
+                        }
                     }
                 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index d099c8e..fa243da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
 
 import android.content.Context
+import com.android.systemui.Flags.statusBarStaticInoutIndicators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.log.table.TableLogBuffer
@@ -38,7 +39,6 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
@@ -101,42 +101,36 @@
             )
 
     /** The wifi activity status. Null if we shouldn't display the activity status. */
-    private val activity: Flow<DataActivityModel> = run {
-        val default = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+    private val activity: Flow<DataActivityModel?> =
         if (!connectivityConstants.shouldShowActivityConfig) {
-                flowOf(default)
+                flowOf(null)
             } else {
                 combine(interactor.activity, interactor.ssid) { activity, ssid ->
                     when (ssid) {
-                        null -> default
+                        null -> null
                         else -> activity
                     }
                 }
             }
-            .distinctUntilChanged()
-            .logDiffsForTable(
-                wifiTableLogBuffer,
-                columnPrefix = "VM.activity",
-                initialValue = default,
-            )
-            .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = default)
-    }
+            .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null)
 
     override val isActivityInViewVisible: Flow<Boolean> =
         activity
-            .map { it.hasActivityIn }
-            .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+            .map { it?.hasActivityIn ?: false }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val isActivityOutViewVisible: Flow<Boolean> =
         activity
-            .map { it.hasActivityOut }
-            .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+            .map { it?.hasActivityOut ?: false }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val isActivityContainerVisible: Flow<Boolean> =
-        combine(isActivityInViewVisible, isActivityOutViewVisible) { activityIn, activityOut ->
-                activityIn || activityOut
+        if (statusBarStaticInoutIndicators()) {
+                flowOf(connectivityConstants.shouldShowActivityConfig)
+            } else {
+                activity.map { it != null && (it.hasActivityIn || it.hasActivityOut) }
             }
-            .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     // TODO(b/238425913): It isn't ideal for the wifi icon to need to know about whether the
     //  airplane icon is visible. Instead, we should have a parent StatusBarSystemIconsViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index fdf59664..20fef92 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -317,10 +317,11 @@
         }
 
         private void loadWallpaperAndDrawFrameInternal() {
-            Trace.beginSection("ImageWallpaper.CanvasEngine#loadWallpaper");
+            Trace.beginSection("WPMS.ImageWallpaper.CanvasEngine#loadWallpaper");
             boolean loadSuccess = false;
             Bitmap bitmap;
             try {
+                Trace.beginSection("WPMS.getBitmapAsUser");
                 bitmap = mWallpaperManager.getBitmapAsUser(
                         mUserTracker.getUserId(), false, getSourceFlag(), true);
                 if (bitmap != null
@@ -333,15 +334,22 @@
                 // be loaded, we will go into a cycle. Don't do a build where the
                 // default wallpaper can't be loaded.
                 Log.w(TAG, "Unable to load wallpaper!", exception);
+                Trace.beginSection("WPMS.clearWallpaper");
                 mWallpaperManager.clearWallpaper(getWallpaperFlags(), mUserTracker.getUserId());
+                Trace.endSection();
 
                 try {
+                    Trace.beginSection("WPMS.getBitmapAsUser_defaultWallpaper");
                     bitmap = mWallpaperManager.getBitmapAsUser(
                             mUserTracker.getUserId(), false, getSourceFlag(), true);
                 } catch (RuntimeException | OutOfMemoryError e) {
                     Log.w(TAG, "Unable to load default wallpaper!", e);
                     bitmap = null;
+                } finally {
+                    Trace.endSection();
                 }
+            } finally {
+                Trace.endSection();
             }
 
             if (bitmap == null) {
@@ -355,15 +363,23 @@
                 loadSuccess = true;
                 // recycle the previously loaded bitmap
                 if (mBitmap != null) {
+                    Trace.beginSection("WPMS.mBitmap.recycle");
                     mBitmap.recycle();
+                    Trace.endSection();
                 }
                 mBitmap = bitmap;
+                Trace.beginSection("WPMS.wallpaperSupportsWcg");
                 mWideColorGamut = mWallpaperManager.wallpaperSupportsWcg(getSourceFlag());
+                Trace.endSection();
 
                 // +2 usages for the color extraction and the delayed unload.
                 mBitmapUsages += 2;
+                Trace.beginSection("WPMS.recomputeColorExtractorMiniBitmap");
                 recomputeColorExtractorMiniBitmap();
+                Trace.endSection();
+                Trace.beginSection("WPMS.drawFrameInternal");
                 drawFrameInternal();
+                Trace.endSection();
 
                 /*
                  * after loading, the bitmap will be unloaded after all these conditions:
diff --git a/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
index b1421b2..e921a59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
@@ -9,30 +9,34 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.res.R
 import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Expect
 import java.util.concurrent.Executor
 import kotlin.math.roundToInt
-import kotlin.test.assertEquals
-import kotlin.test.assertNotNull
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class CameraAvailabilityListenerTest : SysuiTestCase() {
     companion object {
-        const val EXCLUDED_PKG = "test.excluded.package"
-        const val CAMERA_ID_FRONT = "0"
-        const val CAMERA_ID_INNER = "1"
-        const val PROTECTION_PATH_STRING_FRONT = "M 50,50 a 20,20 0 1 0 40,0 a 20,20 0 1 0 -40,0 Z"
-        const val PROTECTION_PATH_STRING_INNER = "M 40,40 a 10,10 0 1 0 20,0 a 10,10 0 1 0 -20,0 Z"
-        val PATH_RECT_FRONT = rectFromPath(pathFromString(PROTECTION_PATH_STRING_FRONT))
-        val PATH_RECT_INNER = rectFromPath(pathFromString(PROTECTION_PATH_STRING_INNER))
+        private const val EXCLUDED_PKG = "test.excluded.package"
+        private const val LOGICAL_CAMERA_ID_NOT_SPECIFIED = ""
+        private const val LOGICAL_CAMERA_ID_REAR = "0"
+        private const val LOGICAL_CAMERA_ID_FRONT = "1"
+        private const val PHYSICAL_CAMERA_ID_OUTER_FRONT = "5"
+        private const val PHYSICAL_CAMERA_ID_INNER_FRONT = "6"
+        private const val PROTECTION_PATH_STRING_OUTER_FRONT =
+            "M 50,50 a 20,20 0 1 0 40,0 a 20,20 0 1 0 -40,0 Z"
+        private const val PROTECTION_PATH_STRING_INNER_FRONT =
+            "M 40,40 a 10,10 0 1 0 20,0 a 10,10 0 1 0 -20,0 Z"
+        private val PATH_RECT_FRONT =
+            rectFromPath(pathFromString(PROTECTION_PATH_STRING_OUTER_FRONT))
+        private val PATH_RECT_INNER =
+            rectFromPath(pathFromString(PROTECTION_PATH_STRING_INNER_FRONT))
 
         private fun pathFromString(pathString: String): Path {
             val spec = pathString.trim()
@@ -58,105 +62,310 @@
         }
     }
 
-    @Mock private lateinit var cameraManager: CameraManager
-    @Mock
-    private lateinit var cameraTransitionCb: CameraAvailabilityListener.CameraTransitionCallback
-    private lateinit var cameraAvailabilityListener: CameraAvailabilityListener
+    @get:Rule val expect: Expect = Expect.create()
+
+    private val cameraManager = mock<CameraManager>()
+    private val cameraTransitionCallback = TestCameraTransitionCallback()
+
+    private lateinit var cameraAvailabilityCallback: CameraManager.AvailabilityCallback
 
     @Before
     fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        context
-            .getOrCreateTestableResources()
-            .addOverride(R.string.config_cameraProtectionExcludedPackages, EXCLUDED_PKG)
-        context
-            .getOrCreateTestableResources()
-            .addOverride(R.string.config_protectedCameraId, CAMERA_ID_FRONT)
-        context
-            .getOrCreateTestableResources()
-            .addOverride(
-                R.string.config_frontBuiltInDisplayCutoutProtection,
-                PROTECTION_PATH_STRING_FRONT
-            )
-        context
-            .getOrCreateTestableResources()
-            .addOverride(R.string.config_protectedInnerCameraId, CAMERA_ID_INNER)
-        context
-            .getOrCreateTestableResources()
-            .addOverride(
-                R.string.config_innerBuiltInDisplayCutoutProtection,
-                PROTECTION_PATH_STRING_INNER
-            )
-
+        overrideResource(R.string.config_cameraProtectionExcludedPackages, EXCLUDED_PKG)
+        setOuterFrontCameraId(LOGICAL_CAMERA_ID_FRONT)
+        setOuterFrontPhysicalCameraId(PHYSICAL_CAMERA_ID_OUTER_FRONT)
+        overrideResource(
+            R.string.config_frontBuiltInDisplayCutoutProtection,
+            PROTECTION_PATH_STRING_OUTER_FRONT
+        )
+        setInnerFrontCameraId(LOGICAL_CAMERA_ID_FRONT)
+        setInnerFrontPhysicalCameraId(PHYSICAL_CAMERA_ID_INNER_FRONT)
+        overrideResource(
+            R.string.config_innerBuiltInDisplayCutoutProtection,
+            PROTECTION_PATH_STRING_INNER_FRONT
+        )
         context.addMockSystemService(CameraManager::class.java, cameraManager)
 
-        cameraAvailabilityListener =
-            CameraAvailabilityListener.Factory.build(context, context.mainExecutor)
-    }
-
-    @Test
-    fun testFrontCamera() {
-        var path: Path? = null
-        var rect: Rect? = null
-        val callback =
-            object : CameraAvailabilityListener.CameraTransitionCallback {
-                override fun onApplyCameraProtection(protectionPath: Path, bounds: Rect) {
-                    path = protectionPath
-                    rect = bounds
-                }
-
-                override fun onHideCameraProtection() {}
+        whenever(
+                cameraManager.registerAvailabilityCallback(
+                    any(Executor::class.java),
+                    any(CameraManager.AvailabilityCallback::class.java)
+                )
+            )
+            .thenAnswer {
+                cameraAvailabilityCallback = it.arguments[1] as CameraManager.AvailabilityCallback
+                return@thenAnswer Unit
             }
-
-        cameraAvailabilityListener.addTransitionCallback(callback)
-        cameraAvailabilityListener.startListening()
-
-        val callbackCaptor = withArgCaptor {
-            verify(cameraManager).registerAvailabilityCallback(any(Executor::class.java), capture())
-        }
-
-        callbackCaptor.onCameraOpened(CAMERA_ID_FRONT, "")
-        assertNotNull(path)
-        assertEquals(PATH_RECT_FRONT, rect)
     }
 
     @Test
-    fun testInnerCamera() {
-        var path: Path? = null
-        var rect: Rect? = null
-        val callback =
-            object : CameraAvailabilityListener.CameraTransitionCallback {
-                override fun onApplyCameraProtection(protectionPath: Path, bounds: Rect) {
-                    path = protectionPath
-                    rect = bounds
-                }
+    fun onCameraOpened_matchesOuterFrontInfo_showsOuterProtection() {
+        setInnerFrontCameraId(LOGICAL_CAMERA_ID_NOT_SPECIFIED)
+        createAndStartSut()
 
-                override fun onHideCameraProtection() {}
-            }
+        openCamera(LOGICAL_CAMERA_ID_FRONT)
 
-        cameraAvailabilityListener.addTransitionCallback(callback)
-        cameraAvailabilityListener.startListening()
-
-        val callbackCaptor = withArgCaptor {
-            verify(cameraManager).registerAvailabilityCallback(any(Executor::class.java), capture())
-        }
-
-        callbackCaptor.onCameraOpened(CAMERA_ID_INNER, "")
-        assertNotNull(path)
-        assertEquals(PATH_RECT_INNER, rect)
+        assertOuterProtectionShowing()
     }
 
     @Test
-    fun testExcludedPackage() {
-        cameraAvailabilityListener.addTransitionCallback(cameraTransitionCb)
-        cameraAvailabilityListener.startListening()
+    fun onCameraOpened_matchesInnerFrontInfo_showsInnerProtection() {
+        setOuterFrontCameraId(LOGICAL_CAMERA_ID_NOT_SPECIFIED)
+        createAndStartSut()
 
-        val callbackCaptor = withArgCaptor {
-            verify(cameraManager).registerAvailabilityCallback(any(Executor::class.java), capture())
+        openCamera(LOGICAL_CAMERA_ID_FRONT)
+
+        assertInnerProtectionShowing()
+    }
+
+    @Test
+    fun onCameraOpened_doesNotMatchAnyProtectionInfo_doesNotShowProtection() {
+        createAndStartSut()
+
+        openCamera(LOGICAL_CAMERA_ID_REAR)
+
+        assertProtectionNotShowing()
+    }
+
+    @Test
+    fun onCameraOpened_matchesInnerAndOuter_innerUnavailable_showsOuterProtection() {
+        val dupeCameraId = "1"
+        setInnerFrontCameraId(dupeCameraId)
+        setOuterFrontCameraId(dupeCameraId)
+        createAndStartSut()
+
+        setPhysicalCameraUnavailable(dupeCameraId, PHYSICAL_CAMERA_ID_INNER_FRONT)
+        openCamera(dupeCameraId)
+
+        assertOuterProtectionShowing()
+    }
+
+    @Test
+    fun onCameraOpened_matchesInnerAndOuter_outerUnavailable_showsInnerFrontProtection() {
+        val dupeCameraId = "1"
+        setInnerFrontCameraId(dupeCameraId)
+        setOuterFrontCameraId(dupeCameraId)
+        createAndStartSut()
+
+        setPhysicalCameraUnavailable(dupeCameraId, PHYSICAL_CAMERA_ID_OUTER_FRONT)
+        openCamera(dupeCameraId)
+
+        assertInnerProtectionShowing()
+    }
+
+    @Test
+    fun onCameraOpened_matchesOuterFrontInfo_packageExcluded_doesNotShowProtection() {
+        setInnerFrontCameraId(LOGICAL_CAMERA_ID_NOT_SPECIFIED)
+        createAndStartSut()
+
+        openCamera(LOGICAL_CAMERA_ID_FRONT, EXCLUDED_PKG)
+
+        assertProtectionNotShowing()
+    }
+
+    @Test
+    fun onCameraOpened_matchesInnerFrontInfo_packageExcluded_doesNotShowProtection() {
+        setOuterFrontCameraId(LOGICAL_CAMERA_ID_NOT_SPECIFIED)
+        createAndStartSut()
+
+        openCamera(LOGICAL_CAMERA_ID_FRONT, EXCLUDED_PKG)
+
+        assertProtectionNotShowing()
+    }
+
+    @Test
+    fun onCameraClosed_matchesActiveOuterFrontProtection_hidesProtection() {
+        setInnerFrontCameraId(LOGICAL_CAMERA_ID_NOT_SPECIFIED)
+        createAndStartSut()
+        openCamera(LOGICAL_CAMERA_ID_FRONT)
+
+        closeCamera(LOGICAL_CAMERA_ID_FRONT)
+
+        assertProtectionNotShowing()
+    }
+
+    @Test
+    fun onCameraClosed_matchesActiveInnerFrontProtection_hidesProtection() {
+        setOuterFrontCameraId(LOGICAL_CAMERA_ID_NOT_SPECIFIED)
+        createAndStartSut()
+        openCamera(LOGICAL_CAMERA_ID_FRONT)
+
+        closeCamera(LOGICAL_CAMERA_ID_FRONT)
+
+        assertProtectionNotShowing()
+    }
+
+    @Test
+    fun onCameraClosed_doesNotMatchActiveOuterFrontProtection_keepsShowingProtection() {
+        setInnerFrontCameraId(LOGICAL_CAMERA_ID_NOT_SPECIFIED)
+        createAndStartSut()
+        openCamera(LOGICAL_CAMERA_ID_FRONT)
+
+        closeCamera(LOGICAL_CAMERA_ID_REAR)
+
+        assertOuterProtectionShowing()
+    }
+
+    @Test
+    fun onCameraClosed_doesNotMatchActiveInnerFrontProtection_keepsShowingProtection() {
+        setOuterFrontCameraId(LOGICAL_CAMERA_ID_NOT_SPECIFIED)
+        createAndStartSut()
+        openCamera(LOGICAL_CAMERA_ID_FRONT)
+
+        closeCamera(LOGICAL_CAMERA_ID_REAR)
+
+        assertInnerProtectionShowing()
+    }
+
+    @Test
+    fun onPhysicalCameraAvailable_cameraOpen_matchesOuterFront_showsOuterFrontProtection() {
+        val logicalCameraId = "1"
+        setOuterFrontCameraId(logicalCameraId)
+        setInnerFrontCameraId(logicalCameraId)
+        createAndStartSut()
+        setPhysicalCameraUnavailable(logicalCameraId, PHYSICAL_CAMERA_ID_OUTER_FRONT)
+        openCamera(logicalCameraId)
+
+        setPhysicalCameraAvailable(logicalCameraId, PHYSICAL_CAMERA_ID_OUTER_FRONT)
+
+        assertOuterProtectionShowing()
+    }
+
+    @Test
+    fun onPhysicalCameraAvailable_cameraClosed_matchesOuterFront_doesNotShowProtection() {
+        val logicalCameraId = "1"
+        setOuterFrontCameraId(logicalCameraId)
+        setInnerFrontCameraId(logicalCameraId)
+        createAndStartSut()
+        setPhysicalCameraUnavailable(logicalCameraId, PHYSICAL_CAMERA_ID_OUTER_FRONT)
+
+        setPhysicalCameraAvailable(logicalCameraId, PHYSICAL_CAMERA_ID_OUTER_FRONT)
+
+        assertProtectionNotShowing()
+    }
+
+    @Test
+    fun onPhysicalCameraAvailable_cameraOpen_matchesInnerFront_showsInnerFrontProtection() {
+        val logicalCameraId = "1"
+        setOuterFrontCameraId(logicalCameraId)
+        setInnerFrontCameraId(logicalCameraId)
+        createAndStartSut()
+        setPhysicalCameraUnavailable(logicalCameraId, PHYSICAL_CAMERA_ID_INNER_FRONT)
+        openCamera(logicalCameraId)
+
+        setPhysicalCameraAvailable(logicalCameraId, PHYSICAL_CAMERA_ID_INNER_FRONT)
+
+        assertInnerProtectionShowing()
+    }
+
+    @Test
+    fun onPhysicalCameraAvailable_cameraClosed_matchesInnerFront_doesNotShowProtection() {
+        val logicalCameraId = "1"
+        setOuterFrontCameraId(logicalCameraId)
+        setInnerFrontCameraId(logicalCameraId)
+        createAndStartSut()
+        setPhysicalCameraUnavailable(logicalCameraId, PHYSICAL_CAMERA_ID_INNER_FRONT)
+
+        setPhysicalCameraAvailable(logicalCameraId, PHYSICAL_CAMERA_ID_INNER_FRONT)
+
+        assertProtectionNotShowing()
+    }
+
+    @Test
+    fun onPhysicalCameraUnavailable_matchesActiveProtection_hidesProtection() {
+        setInnerFrontCameraId(LOGICAL_CAMERA_ID_NOT_SPECIFIED)
+        createAndStartSut()
+        openCamera(LOGICAL_CAMERA_ID_FRONT)
+
+        setPhysicalCameraUnavailable(LOGICAL_CAMERA_ID_FRONT, PHYSICAL_CAMERA_ID_OUTER_FRONT)
+
+        assertProtectionNotShowing()
+    }
+
+    @Test
+    fun onPhysicalCameraUnavailable_doesNotMatchActiveProtection_keepsShowingProtection() {
+        setInnerFrontCameraId(LOGICAL_CAMERA_ID_NOT_SPECIFIED)
+        createAndStartSut()
+        openCamera(LOGICAL_CAMERA_ID_FRONT)
+
+        setPhysicalCameraUnavailable(LOGICAL_CAMERA_ID_FRONT, PHYSICAL_CAMERA_ID_INNER_FRONT)
+
+        assertOuterProtectionShowing()
+    }
+
+    private fun openCamera(logicalCameraId: String, packageId: String = "") {
+        cameraAvailabilityCallback.onCameraOpened(logicalCameraId, packageId)
+    }
+
+    private fun closeCamera(logicalCameraId: String) {
+        cameraAvailabilityCallback.onCameraClosed(logicalCameraId)
+    }
+
+    private fun setPhysicalCameraAvailable(logicalCameraId: String, physicalCameraId: String) {
+        cameraAvailabilityCallback.onPhysicalCameraAvailable(logicalCameraId, physicalCameraId)
+    }
+
+    private fun setPhysicalCameraUnavailable(logicalCameraId: String, physicalCameraId: String) {
+        cameraAvailabilityCallback.onPhysicalCameraUnavailable(logicalCameraId, physicalCameraId)
+    }
+
+    private fun assertOuterProtectionShowing() {
+        expect.that(cameraTransitionCallback.shouldShowProtection).isTrue()
+        expect.that(cameraTransitionCallback.protectionPath).isNotNull()
+        expect.that(cameraTransitionCallback.protectionBounds).isEqualTo(PATH_RECT_FRONT)
+    }
+
+    private fun assertInnerProtectionShowing() {
+        expect.that(cameraTransitionCallback.shouldShowProtection).isTrue()
+        expect.that(cameraTransitionCallback.protectionPath).isNotNull()
+        expect.that(cameraTransitionCallback.protectionBounds).isEqualTo(PATH_RECT_INNER)
+    }
+
+    private fun assertProtectionNotShowing() {
+        expect.that(cameraTransitionCallback.shouldShowProtection).isFalse()
+        expect.that(cameraTransitionCallback.protectionBounds).isNull()
+        expect.that(cameraTransitionCallback.protectionPath).isNull()
+    }
+
+    private fun setOuterFrontCameraId(id: String) {
+        overrideResource(R.string.config_protectedCameraId, id)
+    }
+
+    private fun setOuterFrontPhysicalCameraId(id: String) {
+        overrideResource(R.string.config_protectedPhysicalCameraId, id)
+    }
+
+    private fun setInnerFrontCameraId(id: String) {
+        overrideResource(R.string.config_protectedInnerCameraId, id)
+    }
+
+    private fun setInnerFrontPhysicalCameraId(id: String) {
+        overrideResource(R.string.config_protectedInnerPhysicalCameraId, id)
+    }
+
+    private fun createAndStartSut(): CameraAvailabilityListener {
+        return CameraAvailabilityListener.build(context, context.mainExecutor).also {
+            it.addTransitionCallback(cameraTransitionCallback)
+            it.startListening()
         }
-        callbackCaptor.onCameraOpened(CAMERA_ID_FRONT, EXCLUDED_PKG)
+    }
 
-        verify(cameraTransitionCb, never())
-            .onApplyCameraProtection(any(Path::class.java), any(Rect::class.java))
+    private class TestCameraTransitionCallback :
+        CameraAvailabilityListener.CameraTransitionCallback {
+        var shouldShowProtection = false
+        var protectionPath: Path? = null
+        var protectionBounds: Rect? = null
+
+        override fun onApplyCameraProtection(protectionPath: Path, bounds: Rect) {
+            shouldShowProtection = true
+            this.protectionPath = protectionPath
+            this.protectionBounds = bounds
+        }
+
+        override fun onHideCameraProtection() {
+            shouldShowProtection = false
+            protectionPath = null
+            protectionBounds = null
+        }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 91219f0..0ee0939 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -151,6 +151,7 @@
             )
         udfpsOverlayInteractor =
                 UdfpsOverlayInteractor(
+                        context,
                         authController,
                         selectedUserInteractor,
                         testScope.backgroundScope,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
index bbf471c..6a68672 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
@@ -108,6 +108,7 @@
     private fun createUdpfsOverlayInteractor() {
         underTest =
             UdfpsOverlayInteractor(
+                context,
                 authController,
                 selectedUserInteractor,
                 testScope.backgroundScope
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt
index 863d9eb..fd5a584 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -64,7 +65,8 @@
     init {
         kosmos.deviceEntryIconViewModelTransitionsMock.add(testDeviceEntryIconTransition)
     }
-    val systemUIDialogManager = kosmos.systemUIDialogManager
+    private val systemUIDialogManager = kosmos.systemUIDialogManager
+    private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
     private val underTest = kosmos.deviceEntryUdfpsTouchOverlayViewModel
 
     @Captor
@@ -116,4 +118,16 @@
 
             assertThat(shouldHandleTouches).isTrue()
         }
+
+    @Test
+    fun deviceEntryViewAlphaZero_alternateBouncerVisible_shouldHandleTouchesTrue() =
+        testScope.runTest {
+            val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches)
+
+            testDeviceEntryIconTransitionAlpha.value = 0f
+            runCurrent()
+
+            bouncerRepository.setAlternateVisible(true)
+            assertThat(shouldHandleTouches).isTrue()
+        }
 }
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 7475235..6170e0c 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
@@ -128,6 +128,7 @@
             )
         udfpsOverlayInteractor =
             UdfpsOverlayInteractor(
+                context,
                 authController,
                 selectedUserInteractor,
                 testScope.backgroundScope
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/domain/interactor/ConfigurationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/common/domain/interactor/ConfigurationInteractorTest.kt
deleted file mode 100644
index bfa3641..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/common/domain/interactor/ConfigurationInteractorTest.kt
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-package com.android.systemui.common.domain.interactor
-
-import android.content.res.Configuration
-import android.graphics.Rect
-import android.testing.AndroidTestingRunner
-import android.view.Surface.ROTATION_0
-import android.view.Surface.ROTATION_90
-import android.view.Surface.Rotation
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
-import com.android.systemui.coroutines.collectValues
-import com.android.systemui.statusbar.policy.FakeConfigurationController
-import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-open class ConfigurationInteractorTest : SysuiTestCase() {
-
-    private val testScope = TestScope()
-
-    private val configurationController = FakeConfigurationController()
-    private val configurationRepository =
-        ConfigurationRepositoryImpl(
-            configurationController,
-            context,
-            testScope.backgroundScope,
-            mock()
-        )
-
-    private lateinit var configuration: Configuration
-    private lateinit var underTest: ConfigurationInteractor
-
-    @Before
-    fun setUp() {
-        configuration = context.resources.configuration
-
-        val testableResources = context.getOrCreateTestableResources()
-        testableResources.overrideConfiguration(configuration)
-
-        underTest = ConfigurationInteractorImpl(configurationRepository)
-    }
-
-    @Test
-    fun maxBoundsChange_emitsMaxBoundsChange() =
-        testScope.runTest {
-            val values by collectValues(underTest.naturalMaxBounds)
-
-            updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
-            runCurrent()
-            updateDisplay(width = DISPLAY_WIDTH * 2, height = DISPLAY_HEIGHT * 3)
-            runCurrent()
-
-            assertThat(values)
-                .containsExactly(
-                    Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT),
-                    Rect(0, 0, DISPLAY_WIDTH * 2, DISPLAY_HEIGHT * 3),
-                )
-                .inOrder()
-        }
-
-    @Test
-    fun maxBoundsSameOnConfigChange_doesNotEmitMaxBoundsChange() =
-        testScope.runTest {
-            val values by collectValues(underTest.naturalMaxBounds)
-
-            updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
-            runCurrent()
-            updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
-            runCurrent()
-
-            assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT))
-        }
-
-    @Test
-    fun firstMaxBoundsChange_emitsMaxBoundsChange() =
-        testScope.runTest {
-            val values by collectValues(underTest.naturalMaxBounds)
-
-            updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
-            runCurrent()
-
-            assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT))
-        }
-
-    @Test
-    fun displayRotatedButMaxBoundsTheSame_doesNotEmitNewMaxBoundsChange() =
-        testScope.runTest {
-            val values by collectValues(underTest.naturalMaxBounds)
-
-            updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
-            runCurrent()
-            updateDisplay(width = DISPLAY_HEIGHT, height = DISPLAY_WIDTH, rotation = ROTATION_90)
-            runCurrent()
-
-            assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT))
-        }
-
-    private fun updateDisplay(
-        width: Int = DISPLAY_WIDTH,
-        height: Int = DISPLAY_HEIGHT,
-        @Rotation rotation: Int = ROTATION_0
-    ) {
-        configuration.windowConfiguration.maxBounds.set(Rect(0, 0, width, height))
-        configuration.windowConfiguration.displayRotation = rotation
-
-        configurationController.onConfigurationChanged(configuration)
-    }
-
-    private companion object {
-        private const val DISPLAY_WIDTH = 100
-        private const val DISPLAY_HEIGHT = 200
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
index c5c0208..9e007e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
@@ -16,14 +16,19 @@
 
 package com.android.systemui.common.ui.domain.interactor
 
+import android.content.res.Configuration
+import android.graphics.Rect
+import android.view.Surface
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -35,13 +40,16 @@
 @RunWith(AndroidJUnit4::class)
 class ConfigurationInteractorTest : SysuiTestCase() {
     private lateinit var testScope: TestScope
+    private lateinit var configuration: Configuration
     private lateinit var underTest: ConfigurationInteractor
     private lateinit var configurationRepository: FakeConfigurationRepository
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-
+        configuration = context.resources.configuration
+        val testableResources = context.getOrCreateTestableResources()
+        testableResources.overrideConfiguration(configuration)
         configurationRepository = FakeConfigurationRepository()
         testScope = TestScope()
         underTest = ConfigurationInteractor(configurationRepository)
@@ -79,4 +87,79 @@
             assertThat(dimensionPixelSizes!![resourceId1]).isEqualTo(pixelSize1)
             assertThat(dimensionPixelSizes!![resourceId2]).isEqualTo(pixelSize2)
         }
+
+    @Test
+    fun maxBoundsChange_emitsMaxBoundsChange() =
+        testScope.runTest {
+            val values by collectValues(underTest.naturalMaxBounds)
+
+            updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
+            runCurrent()
+            updateDisplay(width = DISPLAY_WIDTH * 2, height = DISPLAY_HEIGHT * 3)
+            runCurrent()
+
+            assertThat(values)
+                .containsExactly(
+                    Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT),
+                    Rect(0, 0, DISPLAY_WIDTH * 2, DISPLAY_HEIGHT * 3),
+                )
+                .inOrder()
+        }
+
+    @Test
+    fun maxBoundsSameOnConfigChange_doesNotEmitMaxBoundsChange() =
+        testScope.runTest {
+            val values by collectValues(underTest.naturalMaxBounds)
+
+            updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
+            runCurrent()
+            updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
+            runCurrent()
+
+            assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT))
+        }
+
+    @Test
+    fun firstMaxBoundsChange_emitsMaxBoundsChange() =
+        testScope.runTest {
+            val values by collectValues(underTest.naturalMaxBounds)
+
+            updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
+            runCurrent()
+
+            assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT))
+        }
+
+    @Test
+    fun displayRotatedButMaxBoundsTheSame_doesNotEmitNewMaxBoundsChange() =
+        testScope.runTest {
+            val values by collectValues(underTest.naturalMaxBounds)
+
+            updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
+            runCurrent()
+            updateDisplay(
+                width = DISPLAY_HEIGHT,
+                height = DISPLAY_WIDTH,
+                rotation = Surface.ROTATION_90
+            )
+            runCurrent()
+
+            assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT))
+        }
+
+    private fun updateDisplay(
+        width: Int = DISPLAY_WIDTH,
+        height: Int = DISPLAY_HEIGHT,
+        @Surface.Rotation rotation: Int = Surface.ROTATION_0
+    ) {
+        configuration.windowConfiguration.maxBounds.set(Rect(0, 0, width, height))
+        configuration.windowConfiguration.displayRotation = rotation
+
+        configurationRepository.onConfigurationChange(configuration)
+    }
+
+    private companion object {
+        private const val DISPLAY_WIDTH = 100
+        private const val DISPLAY_HEIGHT = 200
+    }
 }
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 22569e2..c864704 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
@@ -30,20 +30,15 @@
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler
-import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationPanelView
-import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.statusbar.gesture.TapGestureDetector
 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
@@ -87,11 +82,6 @@
                 { mock(DeviceEntryForegroundViewModel::class.java) },
                 { mock(DeviceEntryBackgroundViewModel::class.java) },
                 { falsingManager },
-                { mock(AlternateBouncerViewModel::class.java) },
-                { mock(NotificationShadeWindowController::class.java) },
-                TestScope().backgroundScope,
-                { mock(SwipeUpAnywhereGestureHandler::class.java) },
-                { mock(TapGestureDetector::class.java) },
                 { mock(VibratorHelper::class.java) },
             )
     }
@@ -177,21 +167,4 @@
         assertThat(constraint.layout.topMargin).isEqualTo(5)
         assertThat(constraint.layout.startMargin).isEqualTo(4)
     }
-
-    @Test
-    fun deviceEntryIconViewIsAboveAlternateBouncerView() {
-        mSetFlagsRule.enableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
-
-        val constraintLayout = ConstraintLayout(context, null)
-        underTest.addViews(constraintLayout)
-        assertThat(constraintLayout.childCount).isGreaterThan(0)
-        val deviceEntryIconView = constraintLayout.getViewById(R.id.device_entry_icon_view)
-        val alternateBouncerView = constraintLayout.getViewById(R.id.alternate_bouncer)
-        assertThat(deviceEntryIconView).isNotNull()
-        assertThat(alternateBouncerView).isNotNull()
-
-        // device entry icon is above the alternate bouncer
-        assertThat(constraintLayout.indexOfChild(deviceEntryIconView))
-            .isGreaterThan(constraintLayout.indexOfChild(alternateBouncerView))
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
index 1f99303..0a464e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
@@ -22,11 +22,11 @@
 import android.testing.TestableLooper
 import android.view.View
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.media.controls.models.player.MediaViewHolder
 import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder
 import com.android.systemui.media.controls.util.MediaFlags
+import com.android.systemui.res.R
 import com.android.systemui.util.animation.MeasurementInput
 import com.android.systemui.util.animation.TransitionLayout
 import com.android.systemui.util.animation.TransitionViewState
@@ -37,6 +37,7 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.floatThat
 import org.mockito.Mock
+import org.mockito.Mockito.never
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
@@ -183,10 +184,12 @@
         // detail widgets occupy [90, 100]
         whenever(detailWidgetState.y).thenReturn(90F)
         whenever(detailWidgetState.height).thenReturn(10)
+        whenever(detailWidgetState.alpha).thenReturn(1F)
         // control widgets occupy [150, 170]
         whenever(controlWidgetState.y).thenReturn(150F)
         whenever(controlWidgetState.height).thenReturn(20)
-        // in current beizer, when the progress reach 0.38, the result will be 0.5
+        whenever(controlWidgetState.alpha).thenReturn(1F)
+        // in current bezier, when the progress reach 0.38, the result will be 0.5
         mediaViewController.squishViewState(mockViewState, 181.4F / 200F)
         verify(controlWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta }
         verify(detailWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
@@ -196,6 +199,34 @@
     }
 
     @Test
+    fun testSquishViewState_applySquishFraction_toTransitionViewState_alpha_invisibleElements() {
+        whenever(mockViewState.copy()).thenReturn(mockCopiedState)
+        whenever(mockCopiedState.widgetStates)
+            .thenReturn(
+                mutableMapOf(
+                    R.id.media_progress_bar to controlWidgetState,
+                    R.id.header_artist to detailWidgetState
+                )
+            )
+        whenever(mockCopiedState.measureHeight).thenReturn(200)
+        // detail widgets occupy [90, 100]
+        whenever(detailWidgetState.y).thenReturn(90F)
+        whenever(detailWidgetState.height).thenReturn(10)
+        whenever(detailWidgetState.alpha).thenReturn(0F)
+        // control widgets occupy [150, 170]
+        whenever(controlWidgetState.y).thenReturn(150F)
+        whenever(controlWidgetState.height).thenReturn(20)
+        whenever(controlWidgetState.alpha).thenReturn(0F)
+        // Verify that alpha remains 0 throughout squishing
+        mediaViewController.squishViewState(mockViewState, 181.4F / 200F)
+        verify(controlWidgetState, never()).alpha = floatThat { it > 0 }
+        verify(detailWidgetState, never()).alpha = floatThat { it > 0 }
+        mediaViewController.squishViewState(mockViewState, 200F / 200F)
+        verify(controlWidgetState, never()).alpha = floatThat { it > 0 }
+        verify(detailWidgetState, never()).alpha = floatThat { it > 0 }
+    }
+
+    @Test
     fun testSquishViewState_applySquishFraction_toTransitionViewState_alpha_forRecommendation() {
         whenever(mockViewState.copy()).thenReturn(mockCopiedState)
         whenever(mockCopiedState.widgetStates)
@@ -210,12 +241,15 @@
         // media container widgets occupy [20, 300]
         whenever(mediaContainerWidgetState.y).thenReturn(20F)
         whenever(mediaContainerWidgetState.height).thenReturn(280)
+        whenever(mediaContainerWidgetState.alpha).thenReturn(1F)
         // media title widgets occupy [320, 330]
         whenever(mediaTitleWidgetState.y).thenReturn(320F)
         whenever(mediaTitleWidgetState.height).thenReturn(10)
+        whenever(mediaTitleWidgetState.alpha).thenReturn(1F)
         // media subtitle widgets occupy [340, 350]
         whenever(mediaSubTitleWidgetState.y).thenReturn(340F)
         whenever(mediaSubTitleWidgetState.height).thenReturn(10)
+        whenever(mediaSubTitleWidgetState.alpha).thenReturn(1F)
 
         // in current beizer, when the progress reach 0.38, the result will be 0.5
         mediaViewController.squishViewState(mockViewState, 307.6F / 360F)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 4d42324..b7618d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -75,7 +75,6 @@
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runCurrent
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.anyInt
@@ -463,7 +462,6 @@
 
     // region setNoteTaskShortcutEnabled
     @Test
-    @Ignore("b/316332684")
     fun setNoteTaskShortcutEnabled_setTrue() {
         createNoteTaskController().setNoteTaskShortcutEnabled(value = true, userTracker.userHandle)
 
@@ -480,7 +478,6 @@
     }
 
     @Test
-    @Ignore("b/316332684")
     fun setNoteTaskShortcutEnabled_setFalse() {
         createNoteTaskController().setNoteTaskShortcutEnabled(value = false, userTracker.userHandle)
 
@@ -497,7 +494,6 @@
     }
 
     @Test
-    @Ignore("b/316332684")
     fun setNoteTaskShortcutEnabled_workProfileUser_setTrue() {
         whenever(context.createContextAsUser(eq(workUserInfo.userHandle), any()))
             .thenReturn(workProfileContext)
@@ -519,7 +515,6 @@
     }
 
     @Test
-    @Ignore("b/316332684")
     fun setNoteTaskShortcutEnabled_workProfileUser_setFalse() {
         whenever(context.createContextAsUser(eq(workUserInfo.userHandle), any()))
             .thenReturn(workProfileContext)
@@ -738,7 +733,6 @@
 
     // region internalUpdateNoteTaskAsUser
     @Test
-    @Ignore("b/316332684")
     fun updateNoteTaskAsUserInternal_withNotesRole_withShortcuts_shouldUpdateShortcuts() {
         createNoteTaskController(isEnabled = true)
             .launchUpdateNoteTaskAsUser(userTracker.userHandle)
@@ -772,7 +766,6 @@
     }
 
     @Test
-    @Ignore("b/316332684")
     fun updateNoteTaskAsUserInternal_noNotesRole_shouldDisableShortcuts() {
         whenever(roleManager.getRoleHoldersAsUser(ROLE_NOTES, userTracker.userHandle))
             .thenReturn(emptyList())
@@ -796,7 +789,6 @@
     }
 
     @Test
-    @Ignore("b/316332684")
     fun updateNoteTaskAsUserInternal_flagDisabled_shouldDisableShortcuts() {
         createNoteTaskController(isEnabled = false)
             .launchUpdateNoteTaskAsUser(userTracker.userHandle)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
index fbd63c6..8142456 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
@@ -29,6 +29,7 @@
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -300,8 +301,10 @@
         mStateManager.onStartListening();
         mStateManager.executeSetBindService(true);
         mExecutor.runAllReady();
-        mStateManager.onServiceDisconnected(mTileServiceComponentName);
+        mStateManager.onBindingDied(mTileServiceComponentName);
+        mExecutor.runAllReady();
         mClock.advanceTime(5000);
+        mExecutor.runAllReady();
 
         // Two calls: one for the first bind, one for the restart.
         verifyBind(2);
@@ -318,20 +321,66 @@
         mStateManager.onStartListening();
         mStateManager.executeSetBindService(true);
         mExecutor.runAllReady();
-        mStateManager.onServiceDisconnected(mTileServiceComponentName);
+        verify(mMockTileService, times(1)).onStartListening();
+        mStateManager.onBindingDied(mTileServiceComponentName);
+        mExecutor.runAllReady();
 
         // Longer delay than a regular one
         mClock.advanceTime(5000);
-        verifyBind(1);
-        verify(mMockTileService, times(1)).onStartListening();
+        mExecutor.runAllReady();
+
+        assertFalse(mContext.isBound(mTileServiceComponentName));
 
         mClock.advanceTime(20000);
+        mExecutor.runAllReady();
         // Two calls: one for the first bind, one for the restart.
         verifyBind(2);
         verify(mMockTileService, times(2)).onStartListening();
     }
 
     @Test
+    public void testOnServiceDisconnectedDoesnUnbind_doesntForwardToBinder() throws Exception {
+        mStateManager.executeSetBindService(true);
+        mExecutor.runAllReady();
+
+        mStateManager.onStartListening();
+        verify(mMockTileService).onStartListening();
+
+        clearInvocations(mMockTileService);
+        mStateManager.onServiceDisconnected(mTileServiceComponentName);
+        mExecutor.runAllReady();
+
+        mStateManager.onStartListening();
+        verify(mMockTileService, never()).onStartListening();
+    }
+
+    @Test
+    public void testKillProcessLowMemory_unbound_doesntBindAgain() throws Exception {
+        doAnswer(invocation -> {
+            ActivityManager.MemoryInfo memoryInfo = invocation.getArgument(0);
+            memoryInfo.lowMemory = true;
+            return null;
+        }).when(mActivityManager).getMemoryInfo(any());
+        mStateManager.onStartListening();
+        mStateManager.executeSetBindService(true);
+        mExecutor.runAllReady();
+        verifyBind(1);
+        verify(mMockTileService, times(1)).onStartListening();
+
+        mStateManager.onBindingDied(mTileServiceComponentName);
+        mExecutor.runAllReady();
+
+        clearInvocations(mMockTileService);
+        mStateManager.executeSetBindService(false);
+        mExecutor.runAllReady();
+        mClock.advanceTime(30000);
+        mExecutor.runAllReady();
+
+        verifyBind(0);
+        verify(mMockTileService, never()).onStartListening();
+    }
+
+    @Test
     public void testToggleableTile() throws Exception {
         assertTrue(mStateManager.isToggleableTile());
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
index b90ccc0..9941661 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023 The Android Open Source Project
+ * 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.
@@ -16,35 +16,23 @@
 
 package com.android.systemui.scene.shared.flag
 
-import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.FakeFeatureFlagsImpl
-import com.android.systemui.Flags as AconfigFlags
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.compose.ComposeFacade
 import com.android.systemui.flags.Flags
-import com.android.systemui.flags.ReleasedFlag
-import com.android.systemui.flags.ResourceBooleanFlag
-import com.android.systemui.flags.UnreleasedFlag
+import com.android.systemui.flags.setFlagValue
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.media.controls.util.MediaInSceneContainerFlag
-import com.android.systemui.res.R
-import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth
+import org.junit.Assume
 import org.junit.Before
-import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
 
 @SmallTest
-@RunWith(Parameterized::class)
-internal class SceneContainerFlagsTest(
-    private val testCase: TestCase,
-) : SysuiTestCase() {
-
-    @Rule @JvmField val setFlagsRule: SetFlagsRule = SetFlagsRule()
-
-    private lateinit var underTest: SceneContainerFlags
+@RunWith(AndroidJUnit4::class)
+internal class SceneContainerFlagsTest : SysuiTestCase() {
 
     @Before
     fun setUp() {
@@ -52,83 +40,39 @@
         //  Flags.SCENE_CONTAINER_ENABLED is no longer needed.
         val field = Flags::class.java.getField("SCENE_CONTAINER_ENABLED")
         field.isAccessible = true
-        field.set(null, true)
+        field.set(null, true) // note: this does not work with multivalent tests
+    }
 
-        val featureFlags =
-            FakeFeatureFlagsClassic().apply {
-                SceneContainerFlagsImpl.classicFlagTokens.forEach { flagToken ->
-                    when (flagToken) {
-                        is ResourceBooleanFlag -> set(flagToken, testCase.areAllFlagsSet)
-                        is ReleasedFlag -> set(flagToken, testCase.areAllFlagsSet)
-                        is UnreleasedFlag -> set(flagToken, testCase.areAllFlagsSet)
-                        else -> error("Unsupported flag type ${flagToken.javaClass}")
-                    }
-                }
-            }
-        // TODO(b/306421592): get the aconfig FeatureFlags from the SetFlagsRule.
-        val aconfigFlags = FakeFeatureFlagsImpl()
-
+    private fun setAconfigFlagsEnabled(enabled: Boolean) {
         listOf(
-                AconfigFlags.FLAG_SCENE_CONTAINER,
-                AconfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
+                com.android.systemui.Flags.FLAG_SCENE_CONTAINER,
+                com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
                 KeyguardShadeMigrationNssl.FLAG_NAME,
                 MediaInSceneContainerFlag.FLAG_NAME,
             )
-            .forEach { flagToken ->
-                setFlagsRule.enableFlags(flagToken)
-                aconfigFlags.setFlag(flagToken, testCase.areAllFlagsSet)
-                overrideResource(
-                    R.bool.config_sceneContainerFrameworkEnabled,
-                    testCase.isResourceConfigEnabled
-                )
-            }
-
-        underTest =
-            SceneContainerFlagsImpl(
-                context = context,
-                featureFlagsClassic = featureFlags,
-                isComposeAvailable = testCase.isComposeAvailable,
-            )
+            .forEach { flagName -> mSetFlagsRule.setFlagValue(flagName, enabled) }
     }
 
     @Test
-    fun isEnabled() {
-        assertThat(underTest.isEnabled()).isEqualTo(testCase.expectedEnabled)
+    fun isNotEnabled_withoutAconfigFlags() {
+        setAconfigFlagsEnabled(false)
+        Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(false)
+        Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(false)
     }
 
-    internal data class TestCase(
-        val isComposeAvailable: Boolean,
-        val areAllFlagsSet: Boolean,
-        val isResourceConfigEnabled: Boolean,
-        val expectedEnabled: Boolean,
-    ) {
-        override fun toString(): String {
-            return "(compose=$isComposeAvailable + flags=$areAllFlagsSet) + XML" +
-                " config=$isResourceConfigEnabled -> expected=$expectedEnabled"
-        }
+    @Test
+    fun isEnabled_withAconfigFlags_withCompose() {
+        Assume.assumeTrue(ComposeFacade.isComposeAvailable())
+        setAconfigFlagsEnabled(true)
+        Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(true)
+        Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(true)
     }
 
-    companion object {
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun testCases() = buildList {
-            repeat(8) { combination ->
-                val isComposeAvailable = combination and 0b100 != 0
-                val areAllFlagsSet = combination and 0b010 != 0
-                val isResourceConfigEnabled = combination and 0b001 != 0
-
-                val expectedEnabled =
-                    isComposeAvailable && areAllFlagsSet && isResourceConfigEnabled
-
-                add(
-                    TestCase(
-                        isComposeAvailable = isComposeAvailable,
-                        areAllFlagsSet = areAllFlagsSet,
-                        expectedEnabled = expectedEnabled,
-                        isResourceConfigEnabled = isResourceConfigEnabled,
-                    )
-                )
-            }
-        }
+    @Test
+    fun isNotEnabled_withAconfigFlags_withoutCompose() {
+        Assume.assumeFalse(ComposeFacade.isComposeAvailable())
+        setAconfigFlagsEnabled(true)
+        Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(false)
+        Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(false)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
new file mode 100644
index 0000000..5569ca9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -0,0 +1,220 @@
+/*
+ * 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.shade
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.ViewUtils
+import android.view.MotionEvent
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.FakeCommunalRepository
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
+import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableStateFlow
+import org.junit.Assert.assertThrows
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class GlanceableHubContainerControllerTest : SysuiTestCase() {
+    @Mock private lateinit var communalViewModel: CommunalViewModel
+    @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+    @Mock private lateinit var shadeInteractor: ShadeInteractor
+
+    private lateinit var containerView: View
+    private lateinit var testableLooper: TestableLooper
+
+    private lateinit var communalInteractor: CommunalInteractor
+    private lateinit var communalRepository: FakeCommunalRepository
+    private lateinit var underTest: GlanceableHubContainerController
+
+    private val bouncerShowingFlow = MutableStateFlow(false)
+    private val shadeShowingFlow = MutableStateFlow(false)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        val withDeps = CommunalInteractorFactory.create()
+        communalInteractor = withDeps.communalInteractor
+        communalRepository = withDeps.communalRepository
+
+        underTest =
+            GlanceableHubContainerController(
+                communalInteractor,
+                communalViewModel,
+                keyguardTransitionInteractor,
+                shadeInteractor
+            )
+        testableLooper = TestableLooper.get(this)
+
+        communalRepository.setIsCommunalEnabled(true)
+
+        whenever(keyguardTransitionInteractor.isFinishedInStateWhere(any()))
+            .thenReturn(bouncerShowingFlow)
+        whenever(shadeInteractor.isAnyFullyExpanded).thenReturn(shadeShowingFlow)
+
+        overrideResource(R.dimen.communal_grid_gutter_size, SWIPE_REGION_WIDTH)
+    }
+
+    @Test
+    fun isEnabled_interactorEnabled_returnsTrue() {
+        communalRepository.setIsCommunalEnabled(true)
+
+        assertThat(underTest.isEnabled()).isTrue()
+    }
+
+    @Test
+    fun isEnabled_interactorDisabled_returnsFalse() {
+        communalRepository.setIsCommunalEnabled(false)
+
+        assertThat(underTest.isEnabled()).isFalse()
+    }
+
+    @Test
+    fun initView_notEnabled_throwsException() {
+        communalRepository.setIsCommunalEnabled(false)
+
+        assertThrows(RuntimeException::class.java) { underTest.initView(context) }
+    }
+
+    @Test
+    fun initView_calledTwice_throwsException() {
+        // First call succeeds.
+        underTest.initView(context)
+
+        // Second call throws.
+        assertThrows(RuntimeException::class.java) { underTest.initView(context) }
+    }
+
+    @Test
+    fun onTouchEvent_touchInsideGestureRegion_returnsTrue() {
+        // Communal is open.
+        communalRepository.setDesiredScene(CommunalSceneKey.Communal)
+
+        initAndAttachContainerView()
+
+        // Touch events are intercepted.
+        assertThat(underTest.onTouchEvent(DOWN_IN_SWIPE_REGION_EVENT)).isTrue()
+    }
+
+    @Test
+    fun onTouchEvent_subsequentTouchesAfterGestureStart_returnsTrue() {
+        // Communal is open.
+        communalRepository.setDesiredScene(CommunalSceneKey.Communal)
+
+        initAndAttachContainerView()
+
+        // Initial touch down is intercepted, and so are touches outside of the region, until an up
+        // event is received.
+        assertThat(underTest.onTouchEvent(DOWN_IN_SWIPE_REGION_EVENT)).isTrue()
+        assertThat(underTest.onTouchEvent(MOVE_EVENT)).isTrue()
+        assertThat(underTest.onTouchEvent(UP_EVENT)).isTrue()
+        assertThat(underTest.onTouchEvent(MOVE_EVENT)).isFalse()
+    }
+
+    @Test
+    fun onTouchEvent_communalOpen_returnsTrue() {
+        // Communal is open.
+        communalRepository.setDesiredScene(CommunalSceneKey.Communal)
+
+        initAndAttachContainerView()
+        testableLooper.processAllMessages()
+
+        // Touch events are intercepted.
+        assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
+    }
+
+    @Test
+    fun onTouchEvent_communalAndBouncerShowing_returnsFalse() {
+        // Communal is open.
+        communalRepository.setDesiredScene(CommunalSceneKey.Communal)
+
+        initAndAttachContainerView()
+
+        // Bouncer is visible.
+        bouncerShowingFlow.value = true
+        testableLooper.processAllMessages()
+
+        // Touch events are not intercepted.
+        assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
+    }
+
+    @Test
+    fun onTouchEvent_communalAndShadeShowing_returnsFalse() {
+        // Communal is open.
+        communalRepository.setDesiredScene(CommunalSceneKey.Communal)
+
+        initAndAttachContainerView()
+
+        shadeShowingFlow.value = true
+        testableLooper.processAllMessages()
+
+        // Touch events are not intercepted.
+        assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
+    }
+
+    private fun initAndAttachContainerView() {
+        containerView = View(context)
+        // Make view clickable so that dispatchTouchEvent returns true.
+        containerView.isClickable = true
+
+        underTest.initView(containerView)
+        // Attach the view so that flows start collecting.
+        ViewUtils.attachView(containerView)
+        // Give the view a size so that determining if a touch starts at the right edge works.
+        containerView.layout(0, 0, CONTAINER_WIDTH, CONTAINER_HEIGHT)
+    }
+
+    companion object {
+        private const val CONTAINER_WIDTH = 100
+        private const val CONTAINER_HEIGHT = 100
+        private const val SWIPE_REGION_WIDTH = 20
+
+        private val DOWN_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+        private val DOWN_IN_SWIPE_REGION_EVENT =
+            MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, CONTAINER_WIDTH.toFloat(), 0f, 0)
+        private val MOVE_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
+        private val UP_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)
+
+        @BeforeClass
+        @JvmStatic
+        fun beforeClass() {
+            // Glanceable hub requires Compose, no point running any of these tests if compose isn't
+            // enabled.
+            assumeTrue(ComposeFacade.isComposeAvailable())
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 6ff7966..86d8d54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.shade
 
+import android.content.Context
 import android.os.Handler
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
@@ -32,7 +33,6 @@
 import com.android.keyguard.dagger.KeyguardBouncerComponent
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.back.domain.interactor.BackActionInteractor
 import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
 import com.android.systemui.bouncer.data.repository.BouncerMessageRepositoryImpl
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
@@ -45,8 +45,6 @@
 import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.classifier.FalsingCollectorFake
-import com.android.systemui.communal.data.repository.FakeCommunalRepository
-import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.compose.ComposeFacade.isComposeAvailable
 import com.android.systemui.dock.DockManager
 import com.android.systemui.dump.DumpManager
@@ -69,9 +67,12 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
 import com.android.systemui.log.BouncerLogger
-import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
 import com.android.systemui.statusbar.DragDownHelper
@@ -80,6 +81,7 @@
 import com.android.systemui.statusbar.NotificationShadeDepthController
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.gesture.TapGestureDetector
 import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
 import com.android.systemui.statusbar.notification.stack.AmbientState
@@ -94,10 +96,12 @@
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.kotlin.JavaAdapter
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
+import java.util.Optional
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.test.TestScope
@@ -112,9 +116,8 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-import java.util.Optional
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -127,8 +130,6 @@
     @Mock private lateinit var centralSurfaces: CentralSurfaces
     @Mock private lateinit var dozeServiceHost: DozeServiceHost
     @Mock private lateinit var dozeScrimController: DozeScrimController
-    @Mock private lateinit var backActionInteractor: BackActionInteractor
-    @Mock private lateinit var powerInteractor: PowerInteractor
     @Mock private lateinit var dockManager: DockManager
     @Mock private lateinit var notificationPanelViewController: NotificationPanelViewController
     @Mock private lateinit var notificationShadeDepthController: NotificationShadeDepthController
@@ -150,8 +151,7 @@
     @Mock
     private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener
     @Mock private lateinit var notificationInsetsController: NotificationInsetsController
-    @Mock private lateinit var mCommunalViewModel: CommunalViewModel
-    private lateinit var mCommunalRepository: FakeCommunalRepository
+    @Mock private lateinit var mGlanceableHubContainerController: GlanceableHubContainerController
     @Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
     @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
     @Mock lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController
@@ -201,8 +201,6 @@
         featureFlagsClassic.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
         mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
 
-        mCommunalRepository = FakeCommunalRepository()
-
         testScope = TestScope()
         fakeClock = FakeSystemClock()
         underTest =
@@ -236,8 +234,7 @@
                 mock(KeyguardMessageAreaController.Factory::class.java),
                 keyguardTransitionInteractor,
                 primaryBouncerToGoneTransitionViewModel,
-                mCommunalViewModel,
-                mCommunalRepository,
+                mGlanceableHubContainerController,
                 notificationLaunchAnimationInteractor,
                 featureFlagsClassic,
                 fakeClock,
@@ -280,6 +277,12 @@
                 primaryBouncerInteractor,
                 alternateBouncerInteractor,
                 mSelectedUserInteractor,
+                { mock (JavaAdapter::class.java )},
+                { mock(AlternateBouncerViewModel::class.java) },
+                { mock(FalsingManager::class.java) },
+                { mock(SwipeUpAnywhereGestureHandler::class.java) },
+                { mock(TapGestureDetector::class.java) },
+                { mock(AlternateBouncerUdfpsIconViewModel::class.java) },
             )
         underTest.setupExpandedStatusBar()
         underTest.setDragDownHelper(dragDownHelper)
@@ -466,6 +469,16 @@
     }
 
     @Test
+    fun handleDispatchTouchEvent_glanceableHubIntercepts_returnsTrue() {
+        whenever(mGlanceableHubContainerController.onTouchEvent(DOWN_EVENT)).thenReturn(true)
+        underTest.setStatusBarViewController(phoneStatusBarViewController)
+
+        val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)
+
+        assertThat(returnVal).isTrue()
+    }
+
+    @Test
     fun shouldInterceptTouchEvent_statusBarKeyguardViewManagerShouldIntercept() {
         // down event should be intercepted by keyguardViewManager
         whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT))
@@ -546,7 +559,11 @@
             return
         }
 
-        mCommunalRepository.setIsCommunalEnabled(true)
+        whenever(mGlanceableHubContainerController.isEnabled()).thenReturn(true)
+
+        val mockCommunalView = mock(View::class.java)
+        whenever(mGlanceableHubContainerController.initView(any<Context>()))
+            .thenReturn(mockCommunalView)
 
         val mockCommunalPlaceholder = mock(View::class.java)
         val fakeViewIndex = 20
@@ -558,7 +575,7 @@
 
         // Communal view added as a child of the container at the proper index, the stub is removed.
         verify(view).removeView(mockCommunalPlaceholder)
-        verify(view).addView(any(), eq(fakeViewIndex))
+        verify(view).addView(eq(mockCommunalView), eq(fakeViewIndex))
     }
 
     @Test
@@ -567,7 +584,7 @@
             return
         }
 
-        mCommunalRepository.setIsCommunalEnabled(false)
+        whenever(mGlanceableHubContainerController.isEnabled()).thenReturn(false)
 
         val mockCommunalPlaceholder = mock(View::class.java)
         val fakeViewIndex = 20
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 29b1366..d9ff892 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -41,8 +41,6 @@
 import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.classifier.FalsingCollectorFake
-import com.android.systemui.communal.data.repository.FakeCommunalRepository
-import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.dock.DockManager
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.dump.logcatLogBuffer
@@ -57,8 +55,12 @@
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeTrustRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
 import com.android.systemui.log.BouncerLogger
+import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
 import com.android.systemui.statusbar.DragDownHelper
@@ -67,6 +69,7 @@
 import com.android.systemui.statusbar.NotificationShadeDepthController
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.gesture.TapGestureDetector
 import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
 import com.android.systemui.statusbar.notification.stack.AmbientState
@@ -81,12 +84,14 @@
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.kotlin.JavaAdapter
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import java.util.Optional
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
@@ -101,6 +106,7 @@
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
+@ExperimentalCoroutinesApi
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper(setAsMainLooper = true)
 @SmallTest
@@ -142,8 +148,7 @@
     private lateinit var unfoldTransitionProgressProvider:
         Optional<UnfoldTransitionProgressProvider>
     @Mock private lateinit var notificationInsetsController: NotificationInsetsController
-    @Mock private lateinit var mCommunalViewModel: CommunalViewModel
-    private lateinit var mCommunalRepository: FakeCommunalRepository
+    @Mock private lateinit var mGlanceableHubContainerController: GlanceableHubContainerController
     @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     @Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
     @Mock lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
@@ -180,8 +185,6 @@
         whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition)
             .thenReturn(emptyFlow())
 
-        mCommunalRepository = FakeCommunalRepository()
-
         val featureFlags = FakeFeatureFlags()
         featureFlags.set(Flags.TRACKPAD_GESTURE_COMMON, true)
         featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false)
@@ -221,8 +224,7 @@
                 Mockito.mock(KeyguardMessageAreaController.Factory::class.java),
                 keyguardTransitionInteractor,
                 primaryBouncerToGoneTransitionViewModel,
-                mCommunalViewModel,
-                mCommunalRepository,
+                mGlanceableHubContainerController,
                 NotificationLaunchAnimationInteractor(NotificationLaunchAnimationRepository()),
                 featureFlags,
                 FakeSystemClock(),
@@ -265,6 +267,12 @@
                 primaryBouncerInteractor,
                 alternateBouncerInteractor,
                 mSelectedUserInteractor,
+                { Mockito.mock(JavaAdapter::class.java) },
+                { Mockito.mock(AlternateBouncerViewModel::class.java) },
+                { Mockito.mock(FalsingManager::class.java) },
+                { Mockito.mock(SwipeUpAnywhereGestureHandler::class.java) },
+                { Mockito.mock(TapGestureDetector::class.java) },
+                { Mockito.mock(AlternateBouncerUdfpsIconViewModel::class.java) },
             )
 
         controller.setupExpandedStatusBar()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
index a6180ec..f178046 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
@@ -54,6 +54,7 @@
 import com.android.internal.util.ContrastColorUtil;
 import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.NotificationContentDescription;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -183,7 +184,7 @@
                 .build();
         // should be ApplicationInfo
         n.extras.putParcelable(Notification.EXTRA_BUILDER_APPLICATION_INFO, new Bundle());
-        StatusBarIconView.contentDescForNotification(mContext, n);
+        NotificationContentDescription.contentDescForNotification(mContext, n);
 
         // no crash, good
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt
index f3094cd..170f651 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt
@@ -80,7 +80,7 @@
             assertThat(isPulseExpanding).isFalse()
 
             withArgCaptor { verify(mockWakeUpCoordinator).addListener(capture()) }
-                .onPulseExpansionChanged(true)
+                .onPulseExpandingChanged(true)
             runCurrent()
 
             assertThat(isPulseExpanding).isTrue()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index ba5ba2c..ad7dee3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -82,7 +82,6 @@
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
-import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -116,7 +115,6 @@
     private AmbientState mAmbientState;
     private TestableResources mTestableResources;
     @Rule public MockitoRule mockito = MockitoJUnit.rule();
-    @Mock private NotificationsController mNotificationsController;
     @Mock private SysuiStatusBarStateController mBarState;
     @Mock private GroupMembershipManager mGroupMembershipManger;
     @Mock private GroupExpansionManager mGroupExpansionManager;
@@ -193,7 +191,7 @@
         mStackScrollerInternal.initView(getContext(), mNotificationSwipeHelper,
                 mNotificationStackSizeCalculator);
         mStackScroller = spy(mStackScrollerInternal);
-        mStackScroller.setNotificationsController(mNotificationsController);
+        mStackScroller.setResetUserExpandedStatesRunnable(()->{});
         mStackScroller.setEmptyShadeView(mEmptyShadeView);
         when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(true);
         when(mStackScrollLayoutController.getNotificationRoundnessManager())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
index ac20683..4bfd7e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
@@ -23,8 +23,8 @@
 import android.view.Surface.ROTATION_90
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.domain.interactor.ConfigurationInteractorImpl
 import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.power.data.repository.FakePowerRepository
 import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -81,7 +81,7 @@
             testScope.backgroundScope,
             mock()
         )
-    private val configurationInteractor = ConfigurationInteractorImpl(configurationRepository)
+    private val configurationInteractor = ConfigurationInteractor(configurationRepository)
 
     private lateinit var configuration: Configuration
     private lateinit var underTest: HideNotificationsInteractor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index 21774aa..c17a8ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -28,7 +28,6 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.TestMocksModule
 import com.android.systemui.collectLastValue
-import com.android.systemui.common.domain.CommonDomainLayerModule
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FakeFeatureFlagsClassicModule
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -69,7 +68,6 @@
             [
                 SysUITestModule::class,
                 ActivatableNotificationViewModelModule::class,
-                CommonDomainLayerModule::class,
                 FooterViewModelModule::class,
                 HeadlessSystemUserModeModule::class,
                 UnfoldTransitionModule.Bindings::class,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index b0305a1..44fa132 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
 
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
@@ -24,6 +26,7 @@
 import com.android.settingslib.mobile.TelephonyIcons.G
 import com.android.settingslib.mobile.TelephonyIcons.THREE_G
 import com.android.settingslib.mobile.TelephonyIcons.UNKNOWN
+import com.android.systemui.Flags.FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
@@ -557,7 +560,8 @@
         }
 
     @Test
-    fun dataActivity_configOn_testIndicators() =
+    @DisableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
+    fun dataActivity_configOn_testIndicators_staticFlagOff() =
         testScope.runTest {
             // Create a new view model here so the constants are properly read
             whenever(constants.shouldShowActivityConfig).thenReturn(true)
@@ -611,6 +615,61 @@
         }
 
     @Test
+    @EnableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
+    fun dataActivity_configOn_testIndicators_staticFlagOn() =
+        testScope.runTest {
+            // Create a new view model here so the constants are properly read
+            whenever(constants.shouldShowActivityConfig).thenReturn(true)
+            createAndSetViewModel()
+
+            var inVisible: Boolean? = null
+            val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this)
+
+            var outVisible: Boolean? = null
+            val outJob = underTest.activityOutVisible.onEach { outVisible = it }.launchIn(this)
+
+            var containerVisible: Boolean? = null
+            val containerJob =
+                underTest.activityContainerVisible.onEach { containerVisible = it }.launchIn(this)
+
+            repository.dataActivityDirection.value =
+                DataActivityModel(
+                    hasActivityIn = true,
+                    hasActivityOut = false,
+                )
+
+            yield()
+
+            assertThat(inVisible).isTrue()
+            assertThat(outVisible).isFalse()
+            assertThat(containerVisible).isTrue()
+
+            repository.dataActivityDirection.value =
+                DataActivityModel(
+                    hasActivityIn = false,
+                    hasActivityOut = true,
+                )
+
+            assertThat(inVisible).isFalse()
+            assertThat(outVisible).isTrue()
+            assertThat(containerVisible).isTrue()
+
+            repository.dataActivityDirection.value =
+                DataActivityModel(
+                    hasActivityIn = false,
+                    hasActivityOut = false,
+                )
+
+            assertThat(inVisible).isFalse()
+            assertThat(outVisible).isFalse()
+            assertThat(containerVisible).isTrue()
+
+            inJob.cancel()
+            outJob.cancel()
+            containerJob.cancel()
+        }
+
+    @Test
     fun netTypeBackground_flagOff_isNull() =
         testScope.runTest {
             flags.set(NEW_NETWORK_SLICE_UI, false)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeColorInversionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeColorInversionRepository.kt
new file mode 100644
index 0000000..9a3b9e2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeColorInversionRepository.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.os.UserHandle
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+class FakeColorInversionRepository : ColorInversionRepository {
+    private val userMap = mutableMapOf<Int, MutableStateFlow<Boolean>>()
+
+    override fun isEnabled(userHandle: UserHandle): StateFlow<Boolean> {
+        return getFlow(userHandle.identifier)
+    }
+
+    override suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean {
+        getFlow(userHandle.identifier).value = isEnabled
+        return true
+    }
+
+    /** initializes the flow if already not */
+    private fun getFlow(userId: Int): MutableStateFlow<Boolean> {
+        return userMap.getOrPut(userId) { MutableStateFlow(false) }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelKosmos.kt
index 5475659..8b1a1d9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelKosmos.kt
@@ -16,14 +16,18 @@
 
 package com.android.systemui.biometrics.ui.viewmodel
 
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
 import com.android.systemui.keyguard.ui.viewmodel.deviceEntryIconViewModel
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.statusbar.phone.systemUIDialogManager
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 
+@ExperimentalCoroutinesApi
 val Kosmos.deviceEntryUdfpsTouchOverlayViewModel by Fixture {
     DeviceEntryUdfpsTouchOverlayViewModel(
         deviceEntryIconViewModel = deviceEntryIconViewModel,
+        alternateBouncerInteractor = alternateBouncerInteractor,
         systemUIDialogManager = systemUIDialogManager,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt
new file mode 100644
index 0000000..86a4509
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.bouncer.domain.interactor
+
+import com.android.keyguard.keyguardUpdateMonitor
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.statusbar.policy.KeyguardStateControllerImpl
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.time.fakeSystemClock
+
+var Kosmos.alternateBouncerInteractor by
+    Kosmos.Fixture {
+        AlternateBouncerInteractor(
+            statusBarStateController = statusBarStateController,
+            keyguardStateController = mock<KeyguardStateControllerImpl>(),
+            bouncerRepository = keyguardBouncerRepository,
+            fingerprintPropertyRepository = fingerprintPropertyRepository,
+            biometricSettingsRepository = biometricSettingsRepository,
+            systemClock = fakeSystemClock,
+            keyguardUpdateMonitor = keyguardUpdateMonitor,
+            scope = testScope.backgroundScope,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
index 6b38d6e..050c2c9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
@@ -37,7 +37,8 @@
         MutableSharedFlow<Unit>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
     override val onConfigurationChange: Flow<Unit> = _onConfigurationChange.asSharedFlow()
 
-    private val _configurationChangeValues = MutableSharedFlow<Configuration>()
+    private val _configurationChangeValues =
+        MutableSharedFlow<Configuration>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
     override val configurationValues: Flow<Configuration> =
         _configurationChangeValues.asSharedFlow()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelKosmos.kt
new file mode 100644
index 0000000..d9c6e4f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelKosmos.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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.alternateBouncerToAodTransitionViewModel by Fixture {
+    AlternateBouncerToAodTransitionViewModel(
+        interactor = keyguardTransitionInteractor,
+        deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
+        animationFlow = keyguardTransitionAnimationFlow,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelKosmos.kt
new file mode 100644
index 0000000..e4821b0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.alternateBouncerToGoneTransitionViewModel by Fixture {
+    AlternateBouncerToGoneTransitionViewModel(
+        interactor = keyguardTransitionInteractor,
+        bouncerToGoneFlows = bouncerToGoneFlows,
+        animationFlow = keyguardTransitionAnimationFlow,
+    )
+}
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 6557bcf..67e9289 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
@@ -41,7 +41,6 @@
         transitionInteractor = keyguardTransitionInteractor,
         keyguardInteractor = keyguardInteractor,
         viewModel = aodToLockscreenTransitionViewModel,
-        shadeDependentFlows = shadeDependentFlows,
         sceneContainerFlags = sceneContainerFlags,
         keyguardViewController = { statusBarKeyguardViewManager },
         deviceEntryInteractor = deviceEntryInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
new file mode 100644
index 0000000..d705248
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.tiles.impl.custom
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.external.FakeCustomTileStatePersister
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileDefaultsRepository
+import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTilePackageUpdatesRepository
+import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileRepository
+import com.android.systemui.qs.tiles.impl.custom.data.repository.FakePackageManagerAdapterFacade
+
+var Kosmos.tileSpec: TileSpec.CustomTileSpec by Kosmos.Fixture()
+
+val Kosmos.customTileStatePersister: FakeCustomTileStatePersister by
+    Kosmos.Fixture { FakeCustomTileStatePersister() }
+
+val Kosmos.customTileRepository: FakeCustomTileRepository by
+    Kosmos.Fixture {
+        FakeCustomTileRepository(
+            customTileStatePersister,
+            packageManagerAdapterFacade,
+            testScope.testScheduler,
+        )
+    }
+
+val Kosmos.customTileDefaultsRepository: FakeCustomTileDefaultsRepository by
+    Kosmos.Fixture { FakeCustomTileDefaultsRepository() }
+
+val Kosmos.customTilePackagesUpdatesRepository: FakeCustomTilePackageUpdatesRepository by
+    Kosmos.Fixture { FakeCustomTilePackageUpdatesRepository() }
+
+val Kosmos.packageManagerAdapterFacade: FakePackageManagerAdapterFacade by
+    Kosmos.Fixture { FakePackageManagerAdapterFacade(tileSpec) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt
index ccf0391..ba803d8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt
@@ -19,21 +19,21 @@
 import android.os.UserHandle
 import android.service.quicksettings.Tile
 import com.android.systemui.qs.external.FakeCustomTileStatePersister
-import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.flow.Flow
 
 class FakeCustomTileRepository(
-    tileSpec: TileSpec.CustomTileSpec,
     customTileStatePersister: FakeCustomTileStatePersister,
+    private val packageManagerAdapterFacade: FakePackageManagerAdapterFacade,
     testBackgroundContext: CoroutineContext,
 ) : CustomTileRepository {
 
     private val realDelegate: CustomTileRepository =
         CustomTileRepositoryImpl(
-            tileSpec,
+            packageManagerAdapterFacade.tileSpec,
             customTileStatePersister,
+            packageManagerAdapterFacade.packageManagerAdapter,
             testBackgroundContext,
         )
 
@@ -44,6 +44,10 @@
 
     override fun getTile(user: UserHandle): Tile? = realDelegate.getTile(user)
 
+    override suspend fun isTileActive(): Boolean = realDelegate.isTileActive()
+
+    override suspend fun isTileToggleable(): Boolean = realDelegate.isTileToggleable()
+
     override suspend fun updateWithTile(
         user: UserHandle,
         newTile: Tile,
@@ -55,4 +59,8 @@
         defaults: CustomTileDefaults,
         isPersistable: Boolean,
     ) = realDelegate.updateWithDefaults(user, defaults, isPersistable)
+
+    fun setTileActive(isActive: Boolean) = packageManagerAdapterFacade.setIsActive(isActive)
+
+    fun setTileToggleable(isActive: Boolean) = packageManagerAdapterFacade.setIsToggleable(isActive)
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt
new file mode 100644
index 0000000..c9a7655
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.tiles.impl.custom.data.repository
+
+import android.content.pm.ServiceInfo
+import android.os.Bundle
+import com.android.systemui.qs.external.PackageManagerAdapter
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+
+class FakePackageManagerAdapterFacade(
+    val tileSpec: TileSpec.CustomTileSpec,
+    val packageManagerAdapter: PackageManagerAdapter = mock {},
+) {
+
+    private var isToggleable: Boolean = false
+    private var isActive: Boolean = false
+
+    init {
+        whenever(packageManagerAdapter.getServiceInfo(eq(tileSpec.componentName), any()))
+            .thenAnswer {
+                ServiceInfo().apply {
+                    metaData =
+                        Bundle().apply {
+                            putBoolean(
+                                android.service.quicksettings.TileService.META_DATA_TOGGLEABLE_TILE,
+                                isToggleable
+                            )
+                            putBoolean(
+                                android.service.quicksettings.TileService.META_DATA_ACTIVE_TILE,
+                                isActive
+                            )
+                        }
+                }
+            }
+    }
+
+    fun setIsActive(isActive: Boolean) {
+        this.isActive = isActive
+    }
+
+    fun setIsToggleable(isToggleable: Boolean) {
+        this.isToggleable = isToggleable
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/domain/interactor/ConfigurationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/inversion/ColorInversionTileKosmos.kt
similarity index 68%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/common/domain/interactor/ConfigurationInteractorKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/inversion/ColorInversionTileKosmos.kt
index 94d9a72..fad34aa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/domain/interactor/ConfigurationInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/inversion/ColorInversionTileKosmos.kt
@@ -14,12 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.common.domain.interactor
+package com.android.systemui.qs.tiles.impl.inversion
 
-import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.accessibility.qs.QSAccessibilityModule
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.qs.qsEventLogger
 
-val Kosmos.configurationInteractor by Fixture {
-    ConfigurationInteractorImpl(repository = configurationRepository)
-}
+val Kosmos.qsColorInversionTileConfig by
+    Kosmos.Fixture { QSAccessibilityModule.provideColorInversionTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorKosmos.kt
index baca8b2..4232b27 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorKosmos.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.notification.stack.domain.interactor
 
-import com.android.systemui.common.domain.interactor.configurationInteractor
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.power.domain.interactor.powerInteractor
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index 13908f1..7744fca 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -90,6 +90,7 @@
 android.database.Cursor
 android.database.CursorIndexOutOfBoundsException
 android.database.CursorJoiner
+android.database.CursorWindow
 android.database.CursorWrapper
 android.database.DataSetObservable
 android.database.DataSetObserver
@@ -97,6 +98,9 @@
 android.database.MatrixCursor$RowBuilder
 android.database.MergeCursor
 android.database.Observable
+android.database.SQLException
+android.database.sqlite.SQLiteClosable
+android.database.sqlite.SQLiteException
 
 android.text.TextUtils
 android.text.TextUtils$SimpleStringSplitter
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 77a5e3d..a4b2896 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -72,6 +72,7 @@
 import android.content.pm.ShortcutServiceInternal;
 import android.content.pm.SuspendDialogInfo;
 import android.content.pm.UserInfo;
+import android.content.pm.UserPackage;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
@@ -559,10 +560,11 @@
                 onClickIntent = UnlaunchableAppActivity.createInQuietModeDialogIntent(appUserId);
             } else if (provider.maskedBySuspendedPackage) {
                 showBadge = mUserManager.hasBadge(appUserId);
-                final String suspendingPackage = mPackageManagerInternal.getSuspendingPackage(
+                final UserPackage suspendingPackage = mPackageManagerInternal.getSuspendingPackage(
                         appInfo.packageName, appUserId);
                 // TODO(b/281839596): don't rely on platform always meaning suspended by admin.
-                if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) {
+                if (suspendingPackage != null
+                        && PLATFORM_PACKAGE_NAME.equals(suspendingPackage.packageName)) {
                     onClickIntent = mDevicePolicyManagerInternal.createShowAdminSupportIntent(
                             appUserId, true);
                 } else {
diff --git a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
index a159a5e..5a548fd 100644
--- a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
+++ b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
@@ -54,9 +54,7 @@
           "exclude-annotation": "android.support.test.filters.FlakyTest"
         }
       ]
-    }
-  ],
-  "postsubmit": [
+    },
     {
       "name": "CtsVirtualDevicesCameraTestCases",
       "options": [
diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java
index 6940ffe..f24c4cc 100644
--- a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java
+++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -70,7 +70,7 @@
 
             @Override
             public void onProcessCaptureRequest(int streamId, int frameId) throws RemoteException {
-                camera.onProcessCaptureRequest(streamId, frameId, /*metadata=*/ null);
+                camera.onProcessCaptureRequest(streamId, frameId);
             }
 
             @Override
diff --git a/services/core/Android.bp b/services/core/Android.bp
index f5a80d8..5111b08 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -100,10 +100,9 @@
     name: "services.core.unboosted",
     defaults: [
         "platform_service_defaults",
-        "android.hardware.power-java_static",
+        "android.hardware.power-java_shared",
     ],
     srcs: [
-        ":android.hardware.biometrics.face-V4-java-source",
         ":android.hardware.tv.hdmi.connection-V1-java-source",
         ":android.hardware.tv.hdmi.earc-V1-java-source",
         ":statslog-art-java-gen",
@@ -153,7 +152,7 @@
     ],
 
     static_libs: [
-        "android.frameworks.location.altitude-V1-java", // AIDL
+        "android.frameworks.location.altitude-V2-java", // AIDL
         "android.frameworks.vibrator-V1-java", // AIDL
         "android.hardware.authsecret-V1.0-java",
         "android.hardware.authsecret-V1-java",
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 136692e..cac2efb 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -289,11 +289,11 @@
      *
      * @param suspendedPackage The package that has been suspended.
      * @param userId The user for which to check.
-     * @return Name of the package that suspended the given package. Returns {@code null} if the
-     * given package is not currently suspended and the platform package name - i.e.
-     * {@code "android"} - if the package was suspended by a device admin.
+     * @return User id and package name of the package that suspended the given package. Returns
+     * {@code null} if the given package is not currently suspended and the platform package name
+     * - i.e. {@code "android"} - if the package was suspended by a device admin.
      */
-    public abstract String getSuspendingPackage(String suspendedPackage, int userId);
+    public abstract UserPackage getSuspendingPackage(String suspendedPackage, int userId);
 
     /**
      * Suspend or unsuspend packages upon admin request.
@@ -312,13 +312,13 @@
      * suspended application.
      *
      * @param suspendedPackage The package that has been suspended.
-     * @param suspendingPackage
+     * @param suspendingPackage The package responsible for suspension.
      * @param userId The user for which to check.
      * @return A {@link SuspendDialogInfo} object describing the dialog to be shown.
      */
     @Nullable
     public abstract SuspendDialogInfo getSuspendedDialogInfo(String suspendedPackage,
-            String suspendingPackage, int userId);
+            UserPackage suspendingPackage, int userId);
 
     /**
      * Gets any distraction flags set via
@@ -1168,14 +1168,14 @@
     public abstract void clearBlockUninstallForUser(@UserIdInt int userId);
 
     /**
-     * Unsuspends all packages suspended by the given package for the user.
+     * Unsuspends all packages suspended by an admin for the user.
      */
-    public abstract void unsuspendForSuspendingPackage(String suspendingPackage, int userId);
+    public abstract void unsuspendAdminSuspendedPackages(int userId);
 
     /**
-     * Returns {@code true} if the package is suspending any packages for the user.
+     * Returns {@code true} if an admin is suspending any packages for the user.
      */
-    public abstract boolean isSuspendingAnyPackages(String suspendingPackage, int userId);
+    public abstract boolean isAdminSuspendingAnyPackages(int userId);
 
     /**
      * Register to listen for loading progress of an installed package.
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
index 329aac6..9f279b1 100644
--- a/services/core/java/com/android/server/BootReceiver.java
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -48,8 +48,6 @@
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.am.DropboxRateLimiter;
-import com.android.server.os.TombstoneProtos;
-import com.android.server.os.TombstoneProtos.Tombstone;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -62,14 +60,11 @@
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.attribute.PosixFilePermissions;
-import java.util.AbstractMap;
 import java.util.HashMap;
 import java.util.Iterator;
-import java.util.Map;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-import java.util.stream.Collectors;
 
 /**
  * Performs a number of miscellaneous, non-system-critical actions
@@ -337,12 +332,12 @@
      *
      * @param ctx Context
      * @param tombstone path to the tombstone
-     * @param tombstoneProto the parsed proto tombstone
+     * @param proto whether the tombstone is stored as proto
      * @param processName the name of the process corresponding to the tombstone
      * @param tmpFileLock the lock for reading/writing tmp files
      */
     public static void addTombstoneToDropBox(
-                Context ctx, File tombstone, Tombstone tombstoneProto, String processName,
+                Context ctx, File tombstone, boolean proto, String processName,
                 ReentrantLock tmpFileLock) {
         final DropBoxManager db = ctx.getSystemService(DropBoxManager.class);
         if (db == null) {
@@ -352,33 +347,31 @@
 
         // Check if we should rate limit and abort early if needed.
         DropboxRateLimiter.RateLimitResult rateLimitResult =
-                sDropboxRateLimiter.shouldRateLimit(TAG_TOMBSTONE_PROTO_WITH_HEADERS, processName);
+                sDropboxRateLimiter.shouldRateLimit(
+                        proto ? TAG_TOMBSTONE_PROTO_WITH_HEADERS : TAG_TOMBSTONE, processName);
         if (rateLimitResult.shouldRateLimit()) return;
 
         HashMap<String, Long> timestamps = readTimestamps();
         try {
-            // Remove the memory data from the proto.
-            Tombstone tombstoneProtoWithoutMemory = removeMemoryFromTombstone(tombstoneProto);
-
-            final byte[] tombstoneBytes = tombstoneProtoWithoutMemory.toByteArray();
-
-            // Use JNI to call the c++ proto to text converter and add the headers to the tombstone.
-            String tombstoneWithoutMemory = new StringBuilder(getBootHeadersToLogAndUpdate())
-                    .append(rateLimitResult.createHeader())
-                    .append(getTombstoneText(tombstoneBytes))
-                    .toString();
-
-            // Add the tombstone without memory data to dropbox.
-            db.addText(TAG_TOMBSTONE, tombstoneWithoutMemory);
-
-            // Add the tombstone proto to dropbox.
-            if (recordFileTimestamp(tombstone, timestamps)) {
-                tmpFileLock.lock();
-                try {
-                    addAugmentedProtoToDropbox(tombstone, tombstoneBytes, db, rateLimitResult);
-                } finally {
-                    tmpFileLock.unlock();
+            if (proto) {
+                if (recordFileTimestamp(tombstone, timestamps)) {
+                    // We need to attach the count indicating the number of dropped dropbox entries
+                    // due to rate limiting. Do this by enclosing the proto tombsstone in a
+                    // container proto that has the dropped entry count and the proto tombstone as
+                    // bytes (to avoid the complexity of reading and writing nested protos).
+                    tmpFileLock.lock();
+                    try {
+                        addAugmentedProtoToDropbox(tombstone, db, rateLimitResult);
+                    } finally {
+                        tmpFileLock.unlock();
+                    }
                 }
+            } else {
+                // Add the header indicating how many events have been dropped due to rate limiting.
+                final String headers = getBootHeadersToLogAndUpdate()
+                        + rateLimitResult.createHeader();
+                addFileToDropBox(db, timestamps, headers, tombstone.getPath(), LOG_SIZE,
+                                 TAG_TOMBSTONE);
             }
         } catch (IOException e) {
             Slog.e(TAG, "Can't log tombstone", e);
@@ -387,8 +380,11 @@
     }
 
     private static void addAugmentedProtoToDropbox(
-                File tombstone, byte[] tombstoneBytes, DropBoxManager db,
+                File tombstone, DropBoxManager db,
                 DropboxRateLimiter.RateLimitResult rateLimitResult) throws IOException {
+        // Read the proto tombstone file as bytes.
+        final byte[] tombstoneBytes = Files.readAllBytes(tombstone.toPath());
+
         final File tombstoneProtoWithHeaders = File.createTempFile(
                 tombstone.getName(), ".tmp", TOMBSTONE_TMP_DIR);
         Files.setPosixFilePermissions(
@@ -421,8 +417,6 @@
         }
     }
 
-    private static native String getTombstoneText(byte[] tombstoneBytes);
-
     private static void addLastkToDropBox(
             DropBoxManager db, HashMap<String, Long> timestamps,
             String headers, String footers, String filename, int maxSize,
@@ -440,31 +434,6 @@
         addFileWithFootersToDropBox(db, timestamps, headers, footers, filename, maxSize, tag);
     }
 
-    /** Removes memory information from the Tombstone proto. */
-    @VisibleForTesting
-    public static Tombstone removeMemoryFromTombstone(Tombstone tombstoneProto) {
-        Tombstone.Builder tombstoneBuilder = tombstoneProto.toBuilder()
-                .clearMemoryMappings()
-                .clearThreads()
-                .putAllThreads(tombstoneProto.getThreadsMap().entrySet()
-                        .stream()
-                        .map(BootReceiver::clearMemoryDump)
-                        .collect(Collectors.toMap(e->e.getKey(), e->e.getValue())));
-
-        if (tombstoneProto.hasSignalInfo()) {
-            tombstoneBuilder.setSignalInfo(
-                    tombstoneProto.getSignalInfo().toBuilder().clearFaultAdjacentMetadata());
-        }
-
-        return tombstoneBuilder.build();
-    }
-
-    private static AbstractMap.SimpleEntry<Integer, TombstoneProtos.Thread> clearMemoryDump(
-            Map.Entry<Integer, TombstoneProtos.Thread> e) {
-        return new AbstractMap.SimpleEntry<Integer, TombstoneProtos.Thread>(
-            e.getKey(), e.getValue().toBuilder().clearMemoryDump().build());
-    }
-
     private static void addFileToDropBox(
             DropBoxManager db, HashMap<String, Long> timestamps,
             String headers, String filename, int maxSize, String tag) throws IOException {
diff --git a/services/core/java/com/android/server/VpnManagerService.java b/services/core/java/com/android/server/VpnManagerService.java
index 2ba3a1d..1d1e2d9 100644
--- a/services/core/java/com/android/server/VpnManagerService.java
+++ b/services/core/java/com/android/server/VpnManagerService.java
@@ -30,7 +30,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.UserInfo;
-import android.net.ConnectivityManager;
 import android.net.INetd;
 import android.net.IVpnManager;
 import android.net.Network;
@@ -89,8 +88,6 @@
     private final Context mUserAllContext;
 
     private final Dependencies mDeps;
-
-    private final ConnectivityManager mCm;
     private final VpnProfileStore mVpnProfileStore;
     private final INetworkManagementService mNMS;
     private final INetd mNetd;
@@ -164,7 +161,6 @@
         mHandler = mHandlerThread.getThreadHandler();
         mVpnProfileStore = mDeps.getVpnProfileStore();
         mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
-        mCm = mContext.getSystemService(ConnectivityManager.class);
         mNMS = mDeps.getINetworkManagementService();
         mNetd = mDeps.getNetd();
         mUserManager = mContext.getSystemService(UserManager.class);
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index c436c72..fd17261 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -136,6 +136,7 @@
         "media.swcodec", // /apex/com.android.media.swcodec/bin/mediaswcodec
         "media.transcoding", // Media transcoding service
         "com.android.bluetooth",  // Bluetooth service
+        "/apex/com.android.art/bin/artd",  // ART daemon
         "/apex/com.android.os.statsd/bin/statsd",  // Stats daemon
     };
 
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index df8f17a..3ae55271 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -70,7 +70,6 @@
 import static android.os.PowerExemptionManager.REASON_OPT_OUT_REQUESTED;
 import static android.os.PowerExemptionManager.REASON_OP_ACTIVATE_PLATFORM_VPN;
 import static android.os.PowerExemptionManager.REASON_OP_ACTIVATE_VPN;
-import static android.os.PowerExemptionManager.REASON_OTHER;
 import static android.os.PowerExemptionManager.REASON_PACKAGE_INSTALLER;
 import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT;
 import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI;
@@ -127,7 +126,6 @@
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 
 import android.Manifest;
-import android.Manifest.permission;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -1085,7 +1083,7 @@
             // Use that as a shortcut if possible to avoid having to recheck all the conditions.
             final boolean whileInUseAllowsUiJobScheduling =
                     ActivityManagerService.doesReasonCodeAllowSchedulingUserInitiatedJobs(
-                            r.getFgsAllowWIU());
+                            r.getFgsAllowWiu_forStart());
             r.updateAllowUiJobScheduling(whileInUseAllowsUiJobScheduling
                     || mAm.canScheduleUserInitiatedJobs(callingUid, callingPid, callingPackage));
         } else {
@@ -2320,7 +2318,7 @@
 
                     // If the foreground service is not started from TOP process, do not allow it to
                     // have while-in-use location/camera/microphone access.
-                    if (!r.isFgsAllowedWIU()) {
+                    if (!r.isFgsAllowedWiu_forCapabilities()) {
                         Slog.w(TAG,
                                 "Foreground service started from background can not have "
                                         + "location/camera/microphone access: service "
@@ -2436,7 +2434,7 @@
                         // mAllowWhileInUsePermissionInFgs.
                         r.mAllowStartForegroundAtEntering = r.getFgsAllowStart();
                         r.mAllowWhileInUsePermissionInFgsAtEntering =
-                                r.isFgsAllowedWIU();
+                                r.isFgsAllowedWiu_forCapabilities();
                         r.mStartForegroundCount++;
                         r.mFgsEnterTime = SystemClock.uptimeMillis();
                         if (!stopProcStatsOp) {
@@ -2632,7 +2630,7 @@
                 policy.getForegroundServiceTypePolicyInfo(type, defaultToType);
         final @ForegroundServicePolicyCheckCode int code = policy.checkForegroundServiceTypePolicy(
                 mAm.mContext, r.packageName, r.app.uid, r.app.getPid(),
-                r.isFgsAllowedWIU(), policyInfo);
+                r.isFgsAllowedWiu_forStart(), policyInfo);
         RuntimeException exception = null;
         switch (code) {
             case FGS_TYPE_POLICY_CHECK_DEPRECATED: {
@@ -7580,78 +7578,76 @@
      * @param callingUid caller app's uid.
      * @param intent intent to start/bind service.
      * @param r the service to start.
-     * @param isBindService True if it's called from bindService().
+     * @param inBindService True if it's called from bindService().
      * @param forBoundFgs set to true if it's called from Service.startForeground() for a
      *                    service that's not started but bound.
-     * @return true if allow, false otherwise.
      */
     private void setFgsRestrictionLocked(String callingPackage,
             int callingPid, int callingUid, Intent intent, ServiceRecord r, int userId,
-            BackgroundStartPrivileges backgroundStartPrivileges, boolean isBindService,
+            BackgroundStartPrivileges backgroundStartPrivileges, boolean inBindService,
             boolean forBoundFgs) {
 
-        @ReasonCode int allowWIU;
+        @ReasonCode int allowWiu;
         @ReasonCode int allowStart;
 
         // If called from bindService(), do not update the actual fields, but instead
         // keep it in a separate set of fields.
-        if (isBindService) {
-            allowWIU = r.mAllowWIUInBindService;
-            allowStart = r.mAllowStartInBindService;
+        if (inBindService) {
+            allowWiu = r.mAllowWiu_inBindService;
+            allowStart = r.mAllowStart_inBindService;
         } else {
-            allowWIU = r.mAllowWhileInUsePermissionInFgsReasonNoBinding;
-            allowStart = r.mAllowStartForegroundNoBinding;
+            allowWiu = r.mAllowWiu_noBinding;
+            allowStart = r.mAllowStart_noBinding;
         }
 
-        // Check DeviceConfig flag.
-        if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) {
-            if (allowWIU == REASON_DENIED) {
-                // BGFGS start restrictions are disabled. We're allowing while-in-use permissions.
-                // Note REASON_OTHER since there's no other suitable reason.
-                allowWIU = REASON_OTHER;
-            }
-        }
-
-        if ((allowWIU == REASON_DENIED)
-                || (allowStart == REASON_DENIED)) {
+        if ((allowWiu == REASON_DENIED) || (allowStart == REASON_DENIED)) {
             @ReasonCode final int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
                     callingPackage, callingPid, callingUid, r.app, backgroundStartPrivileges);
             // We store them to compare the old and new while-in-use logics to each other.
             // (They're not used for any other purposes.)
-            if (allowWIU == REASON_DENIED) {
-                allowWIU = allowWhileInUse;
+            if (allowWiu == REASON_DENIED) {
+                allowWiu = allowWhileInUse;
             }
             if (allowStart == REASON_DENIED) {
                 allowStart = shouldAllowFgsStartForegroundWithBindingCheckLocked(
                         allowWhileInUse, callingPackage, callingPid, callingUid, intent, r,
-                        backgroundStartPrivileges, isBindService);
+                        backgroundStartPrivileges, inBindService);
             }
         }
 
-        if (isBindService) {
-            r.mAllowWIUInBindService = allowWIU;
-            r.mAllowStartInBindService = allowStart;
+        if (inBindService) {
+            r.mAllowWiu_inBindService = allowWiu;
+            r.mAllowStart_inBindService = allowStart;
         } else {
             if (!forBoundFgs) {
-                // This is for "normal" situation.
-                r.mAllowWhileInUsePermissionInFgsReasonNoBinding = allowWIU;
-                r.mAllowStartForegroundNoBinding = allowStart;
+                // This is for "normal" situation -- either:
+                // - in Context.start[Foreground]Service()
+                // - or, in Service.startForeground() on a started service.
+                r.mAllowWiu_noBinding = allowWiu;
+                r.mAllowStart_noBinding = allowStart;
             } else {
-                // This logic is only for logging, so we only update the "by-binding" fields.
-                if (r.mAllowWIUByBindings == REASON_DENIED) {
-                    r.mAllowWIUByBindings = allowWIU;
+                // Service.startForeground() is called on a service that's not started, but bound.
+                // In this case, we set them to "byBindings", not "noBinding", because
+                // we don't want to use them when we calculate the "legacy" code.
+                //
+                // We don't want to set them to "no binding" codes, because on U-QPR1 and below,
+                // we didn't call setFgsRestrictionLocked() in the code path which sets
+                // forBoundFgs to true, and we wanted to preserve the original behavior in other
+                // places to compare the legacy and new logic.
+                if (r.mAllowWiu_byBindings == REASON_DENIED) {
+                    r.mAllowWiu_byBindings = allowWiu;
                 }
-                if (r.mAllowStartByBindings == REASON_DENIED) {
-                    r.mAllowStartByBindings = allowStart;
+                if (r.mAllowStart_byBindings == REASON_DENIED) {
+                    r.mAllowStart_byBindings = allowStart;
                 }
             }
             // Also do a binding client check, unless called from bindService().
-            if (r.mAllowWIUByBindings == REASON_DENIED) {
-                r.mAllowWIUByBindings =
+            if (r.mAllowWiu_byBindings == REASON_DENIED) {
+                r.mAllowWiu_byBindings =
                         shouldAllowFgsWhileInUsePermissionByBindingsLocked(callingUid);
             }
-            if (r.mAllowStartByBindings == REASON_DENIED) {
-                r.mAllowStartByBindings = r.mAllowWIUByBindings;
+            if (r.mAllowStart_byBindings == REASON_DENIED) {
+                r.mAllowStart_byBindings = r.mAllowWiu_byBindings;
             }
         }
     }
@@ -7660,13 +7656,13 @@
      * Reset various while-in-use and BFSL related information.
      */
     void resetFgsRestrictionLocked(ServiceRecord r) {
-        r.clearFgsAllowWIU();
+        r.clearFgsAllowWiu();
         r.clearFgsAllowStart();
 
         r.mInfoAllowStartForeground = null;
         r.mInfoTempFgsAllowListReason = null;
         r.mLoggedInfoAllowStartForeground = false;
-        r.updateAllowUiJobScheduling(r.isFgsAllowedWIU());
+        r.updateAllowUiJobScheduling(r.isFgsAllowedWiu_forStart());
     }
 
     boolean canStartForegroundServiceLocked(int callingPid, int callingUid, String callingPackage) {
@@ -8284,7 +8280,8 @@
             allowWhileInUsePermissionInFgs = r.mAllowWhileInUsePermissionInFgsAtEntering;
             fgsStartReasonCode = r.mAllowStartForegroundAtEntering;
         } else {
-            allowWhileInUsePermissionInFgs = r.isFgsAllowedWIU();
+            // TODO: Also log "forStart"
+            allowWhileInUsePermissionInFgs = r.isFgsAllowedWiu_forCapabilities();
             fgsStartReasonCode = r.getFgsAllowStart();
         }
         final int callerTargetSdkVersion = r.mRecentCallerApplicationInfo != null
@@ -8323,12 +8320,12 @@
                 mAm.getUidProcessCapabilityLocked(r.mRecentCallingUid),
                 0,
                 0,
-                r.mAllowWhileInUsePermissionInFgsReasonNoBinding,
-                r.mAllowWIUInBindService,
-                r.mAllowWIUByBindings,
-                r.mAllowStartForegroundNoBinding,
-                r.mAllowStartInBindService,
-                r.mAllowStartByBindings,
+                r.mAllowWiu_noBinding,
+                r.mAllowWiu_inBindService,
+                r.mAllowWiu_byBindings,
+                r.mAllowStart_noBinding,
+                r.mAllowStart_inBindService,
+                r.mAllowStart_byBindings,
                 fgsStartApi,
                 fgsRestrictionRecalculated);
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2ee39c57..a80d2fd 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2085,7 +2085,6 @@
                 app.makeActive(mSystemThread.getApplicationThread(), mProcessStats);
                 app.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_SYSTEM);
                 addPidLocked(app);
-                mOomAdjuster.onProcessBeginLocked(app);
                 updateLruProcessLocked(app, false, null);
                 updateOomAdjLocked(OOM_ADJ_REASON_SYSTEM_INIT);
             }
@@ -4624,7 +4623,6 @@
         EventLogTags.writeAmProcBound(app.userId, pid, app.processName);
 
         synchronized (mProcLock) {
-            mOomAdjuster.onProcessBeginLocked(app);
             mOomAdjuster.setAttachingProcessStatesLSP(app);
             clearProcessForegroundLocked(app);
             app.setDebugging(false);
@@ -7104,7 +7102,6 @@
                     sdkSandboxClientAppPackage,
                     new HostingRecord(HostingRecord.HOSTING_TYPE_ADDED_APPLICATION,
                             customProcess != null ? customProcess : info.processName));
-            mOomAdjuster.onProcessBeginLocked(app);
             updateLruProcessLocked(app, false, null);
             updateOomAdjLocked(app, OOM_ADJ_REASON_PROCESS_BEGIN);
         }
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index d0ab287..626b70b 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -275,7 +275,7 @@
     @VisibleForTesting static final String DEFAULT_COMPACT_PROC_STATE_THROTTLE =
             String.valueOf(ActivityManager.PROCESS_STATE_RECEIVER);
     @VisibleForTesting static final long DEFAULT_FREEZER_DEBOUNCE_TIMEOUT = 10_000L;
-    @VisibleForTesting static final boolean DEFAULT_FREEZER_EXEMPT_INST_PKG = true;
+    @VisibleForTesting static final boolean DEFAULT_FREEZER_EXEMPT_INST_PKG = false;
     @VisibleForTesting static final boolean DEFAULT_FREEZER_BINDER_ENABLED = true;
     @VisibleForTesting static final long DEFAULT_FREEZER_BINDER_DIVISOR = 4;
     @VisibleForTesting static final int DEFAULT_FREEZER_BINDER_OFFSET = 500;
diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
index fc8ad6bc..05303f6f 100644
--- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
+++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
@@ -507,7 +507,8 @@
                 r.appInfo.uid,
                 r.shortInstanceName,
                 fgsState, // FGS State
-                r.isFgsAllowedWIU(), // allowWhileInUsePermissionInFgs
+                // TODO: Also log "forStart"
+                r.isFgsAllowedWiu_forCapabilities(), // allowWhileInUsePermissionInFgs
                 r.getFgsAllowStart(), // fgsStartReasonCode
                 r.appInfo.targetSdkVersion,
                 r.mRecentCallingUid,
@@ -535,12 +536,12 @@
                 ActivityManager.PROCESS_CAPABILITY_NONE,
                 apiDurationBeforeFgsStart,
                 apiDurationAfterFgsEnd,
-                r.mAllowWhileInUsePermissionInFgsReasonNoBinding,
-                r.mAllowWIUInBindService,
-                r.mAllowWIUByBindings,
-                r.mAllowStartForegroundNoBinding,
-                r.mAllowStartInBindService,
-                r.mAllowStartByBindings,
+                r.mAllowWiu_noBinding,
+                r.mAllowWiu_inBindService,
+                r.mAllowWiu_byBindings,
+                r.mAllowStart_noBinding,
+                r.mAllowStart_inBindService,
+                r.mAllowStart_byBindings,
                 FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_NA,
                 false
         );
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index b00dcd6..b507a60 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -41,7 +41,6 @@
 import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
 import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
-import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ALLOWLIST;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
@@ -2255,7 +2254,7 @@
 
             if (s.isForeground) {
                 final int fgsType = s.foregroundServiceType;
-                if (s.isFgsAllowedWIU()) {
+                if (s.isFgsAllowedWiu_forCapabilities()) {
                     capabilityFromFGS |=
                             (fgsType & FOREGROUND_SERVICE_TYPE_LOCATION)
                                     != 0 ? PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0;
@@ -3494,8 +3493,8 @@
         int initialCapability =  PROCESS_CAPABILITY_NONE;
         boolean initialCached = true;
         final ProcessStateRecord state = app.mState;
-        final int prevProcState = PROCESS_STATE_UNKNOWN;
-        final int prevAdj = UNKNOWN_ADJ;
+        final int prevProcState = state.getCurRawProcState();
+        final int prevAdj = state.getCurRawAdj();
         // If the process has been marked as foreground, it is starting as the top app (with
         // Zygote#START_AS_TOP_APP_ARG), so boost the thread priority of its default UI thread.
         if (state.hasForegroundActivities()) {
@@ -3782,11 +3781,6 @@
     }
 
     @GuardedBy("mService")
-    void onProcessBeginLocked(@NonNull ProcessRecord app) {
-        // Empty, the OomAdjusterModernImpl will have an implementation.
-    }
-
-    @GuardedBy("mService")
     void onProcessEndLocked(@NonNull ProcessRecord app) {
         // Empty, the OomAdjusterModernImpl will have an implementation.
     }
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index d372108..7cc7c51 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -331,9 +331,9 @@
                 if (mLastNode[prevSlot] == node) {
                     mLastNode[prevSlot] = node.mPrev;
                 }
-                node.unlink();
             }
-            mProcessRecordNodes[newSlot].append(node);
+            // node will be firstly unlinked in the append.
+            append(node, newSlot);
         }
 
         void moveAllNodesTo(int fromSlot, int toSlot) {
@@ -389,7 +389,11 @@
         }
 
         void append(@NonNull ProcessRecord app, int targetSlot) {
-            final ProcessRecordNode node = app.mLinkedNodes[mType];
+            append(app.mLinkedNodes[mType], targetSlot);
+        }
+
+        void append(@NonNull ProcessRecordNode node, int targetSlot) {
+            node.unlink();
             mProcessRecordNodes[targetSlot].append(node);
         }
 
@@ -452,6 +456,9 @@
 
             @VisibleForTesting
             void reset() {
+                if (HEAD.mNext != TAIL) {
+                    HEAD.mNext.mPrev = TAIL.mPrev.mNext = null;
+                }
                 HEAD.mNext = TAIL;
                 TAIL.mPrev = HEAD;
             }
@@ -558,20 +565,6 @@
 
     @GuardedBy("mService")
     @Override
-    void onProcessBeginLocked(@NonNull ProcessRecord app) {
-        // Check one type should be good enough.
-        if (app.mLinkedNodes[ProcessRecordNode.NODE_TYPE_PROC_STATE] == null) {
-            for (int i = 0; i < app.mLinkedNodes.length; i++) {
-                app.mLinkedNodes[i] = new ProcessRecordNode(app);
-            }
-        }
-        if (!app.mLinkedNodes[ProcessRecordNode.NODE_TYPE_PROC_STATE].isLinked()) {
-            linkProcessRecordToList(app);
-        }
-    }
-
-    @GuardedBy("mService")
-    @Override
     void onProcessEndLocked(@NonNull ProcessRecord app) {
         if (app.mLinkedNodes[ProcessRecordNode.NODE_TYPE_PROC_STATE] != null
                 && app.mLinkedNodes[ProcessRecordNode.NODE_TYPE_PROC_STATE].isLinked()) {
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 40b1de6..1412259 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -299,6 +299,11 @@
         SparseBooleanArray lastPids = new SparseBooleanArray(20);
         ActivityManagerService.VolatileDropboxEntryStates volatileDropboxEntriyStates = null;
 
+        if (mApp.isDebugging()) {
+            Slog.i(TAG, "Skipping debugged app ANR: " + this + " " + annotation);
+            return;
+        }
+
         mApp.getWindowProcessController().appEarlyNotResponding(annotation, () -> {
             latencyTracker.waitingOnAMSLockStarted();
             synchronized (mService) {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 3156e9d..e57206e 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2153,6 +2153,7 @@
                     mService.forceStopPackageLocked(app.info.packageName,
                             UserHandle.getAppId(app.uid),
                             false, false, true, false, false, false, app.userId, "start failure");
+                    app.doEarlyCleanupIfNecessaryLocked();
                 }
             }
         };
@@ -2782,6 +2783,7 @@
             }
             noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
                     ApplicationExitInfo.SUBREASON_INVALID_START, reason);
+            app.doEarlyCleanupIfNecessaryLocked();
             return false;
         }
         mService.mBatteryStatsService.noteProcessStart(app.processName, app.info.uid);
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index b4cd6a3..e5c4a66 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -621,6 +621,34 @@
         mWindowProcessController = new WindowProcessController(
                 mService.mActivityTaskManager, info, processName, uid, userId, this, this);
         mPkgList.put(_info.packageName, new ProcessStats.ProcessStateHolder(_info.longVersionCode));
+        updateProcessRecordNodes(this);
+    }
+
+    /**
+     * Helper function to let test cases update the pointers.
+     */
+    @VisibleForTesting
+    static void updateProcessRecordNodes(@NonNull ProcessRecord app) {
+        if (app.mService.mConstants.ENABLE_NEW_OOMADJ) {
+            for (int i = 0; i < app.mLinkedNodes.length; i++) {
+                app.mLinkedNodes[i] = new ProcessRecordNode(app);
+            }
+        }
+    }
+
+    /**
+     * Perform cleanups if the process record is going to be discarded in an early
+     * stage of the process lifecycle, specifically when the process has not even
+     * attached itself to the system_server.
+     */
+    @GuardedBy("mService")
+    void doEarlyCleanupIfNecessaryLocked() {
+        if (getThread() == null) {
+            // It's not even attached, make sure we unlink its process nodes.
+            mService.mOomAdjuster.onProcessEndLocked(this);
+        } else {
+            // Let the binder died callback handle the cleanup.
+        }
     }
 
     void resetCrashingOnRestart() {
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 0fba739..08b129e 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -37,6 +37,9 @@
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -81,6 +84,37 @@
     // Maximum number of times it can fail during execution before giving up.
     static final int MAX_DONE_EXECUTING_COUNT = 6;
 
+
+    // Compat IDs for the new FGS logic. For now, we just disable all of them.
+    // TODO: Enable them at some point, but only for V+ builds.
+
+    /**
+     * Compat ID to enable the new FGS start logic, for permission calculation used for
+     * per-FGS-type eligibility calculation.
+     * (See also android.app.ForegroundServiceTypePolicy)
+     */
+    @ChangeId
+    // @EnabledAfter(targetSdkVersion = VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @Disabled
+    static final long USE_NEW_WIU_LOGIC_FOR_START = 311208629L;
+
+    /**
+     * Compat ID to enable the new FGS start logic, for capability calculation.
+     */
+    @ChangeId
+    // Always enabled
+    @Disabled
+    static final long USE_NEW_WIU_LOGIC_FOR_CAPABILITIES = 313677553L;
+
+    /**
+     * Compat ID to enable the new FGS start logic, for deciding whether to allow FGS start from
+     * the background.
+     */
+    @ChangeId
+    // @EnabledAfter(targetSdkVersion = VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @Disabled
+    static final long USE_NEW_BFSL_LOGIC = 311208749L;
+
     final ActivityManagerService ams;
     final ComponentName name; // service component.
     final ComponentName instanceName; // service component's per-instance name.
@@ -178,7 +212,7 @@
     // If it's not DENIED, while-in-use permissions are allowed.
     // while-in-use permissions in FGS started from background might be restricted.
     @PowerExemptionManager.ReasonCode
-    int mAllowWhileInUsePermissionInFgsReasonNoBinding = REASON_DENIED;
+    int mAllowWiu_noBinding = REASON_DENIED;
 
     // A copy of mAllowWhileInUsePermissionInFgs's value when the service is entering FGS state.
     boolean mAllowWhileInUsePermissionInFgsAtEntering;
@@ -208,7 +242,7 @@
     // allow the service becomes foreground service? Service started from background may not be
     // allowed to become a foreground service.
     @PowerExemptionManager.ReasonCode
-    int mAllowStartForegroundNoBinding = REASON_DENIED;
+    int mAllowStart_noBinding = REASON_DENIED;
     // A copy of mAllowStartForeground's value when the service is entering FGS state.
     @PowerExemptionManager.ReasonCode
     int mAllowStartForegroundAtEntering = REASON_DENIED;
@@ -220,72 +254,172 @@
     boolean mLoggedInfoAllowStartForeground;
 
     @PowerExemptionManager.ReasonCode
-    int mAllowWIUInBindService = REASON_DENIED;
+    int mAllowWiu_inBindService = REASON_DENIED;
 
     @PowerExemptionManager.ReasonCode
-    int mAllowWIUByBindings = REASON_DENIED;
+    int mAllowWiu_byBindings = REASON_DENIED;
 
     @PowerExemptionManager.ReasonCode
-    int mAllowStartInBindService = REASON_DENIED;
+    int mAllowStart_inBindService = REASON_DENIED;
 
     @PowerExemptionManager.ReasonCode
-    int mAllowStartByBindings = REASON_DENIED;
+    int mAllowStart_byBindings = REASON_DENIED;
 
-    @PowerExemptionManager.ReasonCode
-    int getFgsAllowWIU() {
-        return mAllowWhileInUsePermissionInFgsReasonNoBinding != REASON_DENIED
-                ? mAllowWhileInUsePermissionInFgsReasonNoBinding
-                : mAllowWIUInBindService;
+    /**
+     * Whether to use the new "while-in-use permission" logic for FGS start
+     */
+    private boolean useNewWiuLogic_forStart() {
+        return Flags.newFgsRestrictionLogic() // This flag should only be set on V+
+                && CompatChanges.isChangeEnabled(USE_NEW_WIU_LOGIC_FOR_START, appInfo.uid);
     }
 
-    boolean isFgsAllowedWIU() {
-        return getFgsAllowWIU() != REASON_DENIED;
+    /**
+     * Whether to use the new "while-in-use permission" logic for capabilities
+     */
+    private boolean useNewWiuLogic_forCapabilities() {
+        return Flags.newFgsRestrictionLogic() // This flag should only be set on V+
+                && CompatChanges.isChangeEnabled(USE_NEW_WIU_LOGIC_FOR_CAPABILITIES, appInfo.uid);
     }
 
+    /**
+     * Whether to use the new "FGS BG start exemption" logic.
+     */
+    private boolean useNewBfslLogic() {
+        return Flags.newFgsRestrictionLogic() // This flag should only be set on V+
+                && CompatChanges.isChangeEnabled(USE_NEW_BFSL_LOGIC, appInfo.uid);
+    }
+
+
+    @PowerExemptionManager.ReasonCode
+    private int getFgsAllowWiu_legacy() {
+        // In the legacy mode (U-), we use mAllowWiu_inBindService and mAllowWiu_noBinding.
+        return reasonOr(
+                mAllowWiu_noBinding,
+                mAllowWiu_inBindService
+        );
+    }
+
+    @PowerExemptionManager.ReasonCode
+    private int getFgsAllowWiu_new() {
+        // In the new mode, use by-bindings instead of in-bind-service
+        return reasonOr(
+                mAllowWiu_noBinding,
+                mAllowWiu_byBindings
+        );
+    }
+
+    /**
+     * We use this logic for ForegroundServiceTypePolicy and UIDT eligibility check.
+     */
+    @PowerExemptionManager.ReasonCode
+    int getFgsAllowWiu_forStart() {
+        if (useNewWiuLogic_forStart()) {
+            return getFgsAllowWiu_new();
+        } else {
+            return getFgsAllowWiu_legacy();
+        }
+    }
+
+    /**
+     * We use this logic for the capability calculation in oom-adjuster.
+     */
+    @PowerExemptionManager.ReasonCode
+    int getFgsAllowWiu_forCapabilities() {
+        if (useNewWiuLogic_forCapabilities()) {
+            return getFgsAllowWiu_new();
+        } else {
+            // If alwaysUseNewLogicForWiuCapabilities() isn't set, just use the same logic as
+            // getFgsAllowWiu_forStart().
+            return getFgsAllowWiu_forStart();
+        }
+    }
+
+    /**
+     * We use this logic for ForegroundServiceTypePolicy and UIDT eligibility check.
+     */
+    boolean isFgsAllowedWiu_forStart() {
+        return getFgsAllowWiu_forStart() != REASON_DENIED;
+    }
+
+    /**
+     * We use this logic for the capability calculation in oom-adjuster.
+     */
+    boolean isFgsAllowedWiu_forCapabilities() {
+        return getFgsAllowWiu_forCapabilities() != REASON_DENIED;
+    }
+
+    @PowerExemptionManager.ReasonCode
+    private int getFgsAllowStart_legacy() {
+        // This is used for BFSL (background FGS launch) exemption.
+        // Originally -- on U-QPR1 and before -- we only used [in-bind-service] + [no-binding].
+        // This would exclude certain "valid" situations, so in U-QPR2, we started
+        // using [by-bindings] too.
+        return reasonOr(
+                mAllowStart_noBinding,
+                mAllowStart_inBindService,
+                mAllowStart_byBindings
+        );
+    }
+
+    @PowerExemptionManager.ReasonCode
+    private int getFgsAllowStart_new() {
+        // In the new mode, we don't use [in-bind-service].
+        return reasonOr(
+                mAllowStart_noBinding,
+                mAllowStart_byBindings
+        );
+    }
+
+    /**
+     * Calculate a BFSL exemption code.
+     */
     @PowerExemptionManager.ReasonCode
     int getFgsAllowStart() {
-        return mAllowStartForegroundNoBinding != REASON_DENIED
-                ? mAllowStartForegroundNoBinding
-                : (mAllowStartByBindings != REASON_DENIED
-                ? mAllowStartByBindings
-                : mAllowStartInBindService);
+        if (useNewBfslLogic()) {
+            return getFgsAllowStart_new();
+        } else {
+            return getFgsAllowStart_legacy();
+        }
     }
 
+    /**
+     * Return whether BFSL is allowed or not.
+     */
     boolean isFgsAllowedStart() {
         return getFgsAllowStart() != REASON_DENIED;
     }
 
-    void clearFgsAllowWIU() {
-        mAllowWhileInUsePermissionInFgsReasonNoBinding = REASON_DENIED;
-        mAllowWIUInBindService = REASON_DENIED;
-        mAllowWIUByBindings = REASON_DENIED;
+    void clearFgsAllowWiu() {
+        mAllowWiu_noBinding = REASON_DENIED;
+        mAllowWiu_inBindService = REASON_DENIED;
+        mAllowWiu_byBindings = REASON_DENIED;
     }
 
     void clearFgsAllowStart() {
-        mAllowStartForegroundNoBinding = REASON_DENIED;
-        mAllowStartInBindService = REASON_DENIED;
-        mAllowStartByBindings = REASON_DENIED;
+        mAllowStart_noBinding = REASON_DENIED;
+        mAllowStart_inBindService = REASON_DENIED;
+        mAllowStart_byBindings = REASON_DENIED;
     }
 
     @PowerExemptionManager.ReasonCode
-    int reasonOr(@PowerExemptionManager.ReasonCode int first,
+    static int reasonOr(
+            @PowerExemptionManager.ReasonCode int first,
             @PowerExemptionManager.ReasonCode int second) {
         return first != REASON_DENIED ? first : second;
     }
 
-    boolean allowedChanged(@PowerExemptionManager.ReasonCode int first,
-            @PowerExemptionManager.ReasonCode int second) {
-        return (first == REASON_DENIED) != (second == REASON_DENIED);
+    @PowerExemptionManager.ReasonCode
+    static int reasonOr(
+            @PowerExemptionManager.ReasonCode int first,
+            @PowerExemptionManager.ReasonCode int second,
+            @PowerExemptionManager.ReasonCode int third) {
+        return first != REASON_DENIED ? first : reasonOr(second, third);
     }
 
-    String changeMessage(@PowerExemptionManager.ReasonCode int first,
-            @PowerExemptionManager.ReasonCode int second) {
-        return reasonOr(first, second) == REASON_DENIED ? "DENIED"
-                : ("ALLOWED ("
-                + reasonCodeToString(first)
-                + "+"
-                + reasonCodeToString(second)
-                + ")");
+    boolean allowedChanged(
+            @PowerExemptionManager.ReasonCode int legacyCode,
+            @PowerExemptionManager.ReasonCode int newCode) {
+        return (legacyCode == REASON_DENIED) != (newCode == REASON_DENIED);
     }
 
     private String getFgsInfoForWtf() {
@@ -295,15 +429,14 @@
     }
 
     void maybeLogFgsLogicChange() {
-        final int origWiu = reasonOr(mAllowWhileInUsePermissionInFgsReasonNoBinding,
-                mAllowWIUInBindService);
-        final int newWiu = reasonOr(mAllowWhileInUsePermissionInFgsReasonNoBinding,
-                mAllowWIUByBindings);
-        final int origStart = reasonOr(mAllowStartForegroundNoBinding, mAllowStartInBindService);
-        final int newStart = reasonOr(mAllowStartForegroundNoBinding, mAllowStartByBindings);
+        final int wiuLegacy = getFgsAllowWiu_legacy();
+        final int wiuNew = getFgsAllowWiu_new();
 
-        final boolean wiuChanged = allowedChanged(origWiu, newWiu);
-        final boolean startChanged = allowedChanged(origStart, newStart);
+        final int startLegacy = getFgsAllowStart_legacy();
+        final int startNew = getFgsAllowStart_new();
+
+        final boolean wiuChanged = allowedChanged(wiuLegacy, wiuNew);
+        final boolean startChanged = allowedChanged(startLegacy, startNew);
 
         if (!wiuChanged && !startChanged) {
             return;
@@ -311,15 +444,14 @@
         final String message = "FGS logic changed:"
                 + (wiuChanged ? " [WIU changed]" : "")
                 + (startChanged ? " [BFSL changed]" : "")
-                + " OW:" // Orig-WIU
-                + changeMessage(mAllowWhileInUsePermissionInFgsReasonNoBinding,
-                        mAllowWIUInBindService)
-                + " NW:" // New-WIU
-                + changeMessage(mAllowWhileInUsePermissionInFgsReasonNoBinding, mAllowWIUByBindings)
-                + " OS:" // Orig-start
-                + changeMessage(mAllowStartForegroundNoBinding, mAllowStartInBindService)
-                + " NS:" // New-start
-                + changeMessage(mAllowStartForegroundNoBinding, mAllowStartByBindings)
+                + " Orig WIU:"
+                + reasonCodeToString(wiuLegacy)
+                + " New WIU:"
+                + reasonCodeToString(wiuNew)
+                + " Orig BFSL:"
+                + reasonCodeToString(startLegacy)
+                + " New BFSL:"
+                + reasonCodeToString(startNew)
                 + getFgsInfoForWtf();
         Slog.wtf(TAG_SERVICE, message);
     }
@@ -587,6 +719,7 @@
                 proto.write(ServiceRecordProto.AppInfo.RES_DIR, appInfo.publicSourceDir);
             }
             proto.write(ServiceRecordProto.AppInfo.DATA_DIR, appInfo.dataDir);
+            proto.write(ServiceRecordProto.AppInfo.TARGET_SDK_VERSION, appInfo.targetSdkVersion);
             proto.end(appInfoToken);
         }
         if (app != null) {
@@ -611,8 +744,10 @@
         ProtoUtils.toDuration(proto, ServiceRecordProto.LAST_ACTIVITY_TIME, lastActivity, now);
         ProtoUtils.toDuration(proto, ServiceRecordProto.RESTART_TIME, restartTime, now);
         proto.write(ServiceRecordProto.CREATED_FROM_FG, createdFromFg);
+
+        // TODO: Log "forStart" too.
         proto.write(ServiceRecordProto.ALLOW_WHILE_IN_USE_PERMISSION_IN_FGS,
-                isFgsAllowedWIU());
+                isFgsAllowedWiu_forCapabilities());
 
         if (startRequested || delayedStop || lastStartId != 0) {
             long startToken = proto.start(ServiceRecordProto.START);
@@ -691,15 +826,25 @@
             proto.end(shortFgsToken);
         }
 
+        // TODO: Dump all the mAllowWiu* and mAllowStart* fields as needed.
+
         proto.end(token);
     }
 
+    void dumpReasonCode(PrintWriter pw, String prefix, String fieldName, int code) {
+        pw.print(prefix);
+        pw.print(fieldName);
+        pw.print("=");
+        pw.println(PowerExemptionManager.reasonCodeToString(code));
+    }
+
     void dump(PrintWriter pw, String prefix) {
         pw.print(prefix); pw.print("intent={");
                 pw.print(intent.getIntent().toShortString(false, true, false, false));
                 pw.println('}');
         pw.print(prefix); pw.print("packageName="); pw.println(packageName);
         pw.print(prefix); pw.print("processName="); pw.println(processName);
+        pw.print(prefix); pw.print("targetSdkVersion="); pw.println(appInfo.targetSdkVersion);
         if (permission != null) {
             pw.print(prefix); pw.print("permission="); pw.println(permission);
         }
@@ -727,26 +872,38 @@
             pw.print(prefix); pw.print("mIsAllowedBgActivityStartsByStart=");
             pw.println(mBackgroundStartPrivilegesByStartMerged);
         }
-        pw.print(prefix); pw.print("mAllowWhileInUsePermissionInFgsReason=");
-        pw.println(PowerExemptionManager.reasonCodeToString(
-                mAllowWhileInUsePermissionInFgsReasonNoBinding));
 
-        pw.print(prefix); pw.print("mAllowWIUInBindService=");
-        pw.println(PowerExemptionManager.reasonCodeToString(mAllowWIUInBindService));
-        pw.print(prefix); pw.print("mAllowWIUByBindings=");
-        pw.println(PowerExemptionManager.reasonCodeToString(mAllowWIUByBindings));
+        pw.print(prefix); pw.print("useNewWiuLogic_forCapabilities()=");
+        pw.println(useNewWiuLogic_forCapabilities());
+        pw.print(prefix); pw.print("useNewWiuLogic_forStart()=");
+        pw.println(useNewWiuLogic_forStart());
+        pw.print(prefix); pw.print("useNewBfslLogic()=");
+        pw.println(useNewBfslLogic());
+
+        dumpReasonCode(pw, prefix, "mAllowWiu_noBinding", mAllowWiu_noBinding);
+        dumpReasonCode(pw, prefix, "mAllowWiu_inBindService", mAllowWiu_inBindService);
+        dumpReasonCode(pw, prefix, "mAllowWiu_byBindings", mAllowWiu_byBindings);
+
+        dumpReasonCode(pw, prefix, "getFgsAllowWiu_legacy", getFgsAllowWiu_legacy());
+        dumpReasonCode(pw, prefix, "getFgsAllowWiu_new", getFgsAllowWiu_new());
+
+        dumpReasonCode(pw, prefix, "getFgsAllowWiu_forStart", getFgsAllowWiu_forStart());
+        dumpReasonCode(pw, prefix, "getFgsAllowWiu_forCapabilities",
+                getFgsAllowWiu_forCapabilities());
 
         pw.print(prefix); pw.print("allowUiJobScheduling="); pw.println(mAllowUiJobScheduling);
         pw.print(prefix); pw.print("recentCallingPackage=");
                 pw.println(mRecentCallingPackage);
         pw.print(prefix); pw.print("recentCallingUid=");
         pw.println(mRecentCallingUid);
-        pw.print(prefix); pw.print("allowStartForeground=");
-        pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartForegroundNoBinding));
-        pw.print(prefix); pw.print("mAllowStartInBindService=");
-        pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartInBindService));
-        pw.print(prefix); pw.print("mAllowStartByBindings=");
-        pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartByBindings));
+
+        dumpReasonCode(pw, prefix, "mAllowStart_noBinding", mAllowStart_noBinding);
+        dumpReasonCode(pw, prefix, "mAllowStart_inBindService", mAllowStart_inBindService);
+        dumpReasonCode(pw, prefix, "mAllowStart_byBindings", mAllowStart_byBindings);
+
+        dumpReasonCode(pw, prefix, "getFgsAllowStart_legacy", getFgsAllowStart_legacy());
+        dumpReasonCode(pw, prefix, "getFgsAllowStart_new", getFgsAllowStart_new());
+        dumpReasonCode(pw, prefix, "getFgsAllowStart", getFgsAllowStart());
 
         pw.print(prefix); pw.print("startForegroundCount=");
         pw.println(mStartForegroundCount);
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index a6b532c..badd7f0 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -3050,8 +3050,8 @@
 
     /**
      * Returns whether the given user requires credential entry at this time. This is used to
-     * intercept activity launches for locked work apps due to work challenge being triggered
-     * or when the profile user is yet to be unlocked.
+     * intercept activity launches for apps corresponding to locked profiles due to separate
+     * challenge being triggered or when the profile user is yet to be unlocked.
      */
     protected boolean shouldConfirmCredentials(@UserIdInt int userId) {
         if (getStartedUserState(userId) == null) {
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index d9e8ddd..654aebd 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -28,3 +28,10 @@
     description: "Restrict network access for certain applications in BFGS process state"
     bug: "304347838"
 }
+# Whether to use the new while-in-use / BG-FGS-start logic
+flag {
+     namespace: "backstage_power"
+     name: "new_fgs_restriction_logic"
+     description: "Enable the new FGS restriction logic"
+     bug: "276963716"
+}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 8091753..dada72e 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -56,6 +56,7 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.Pair;
 import android.util.PrintWriterPrinter;
 
 import com.android.internal.annotations.GuardedBy;
@@ -1640,7 +1641,7 @@
         return mBtHelper.getLeAudioDeviceGroupId(device);
     }
 
-    /*package*/ List<String> getLeAudioGroupAddresses(int groupId) {
+    /*package*/ List<Pair<String, String>> getLeAudioGroupAddresses(int groupId) {
         return mBtHelper.getLeAudioGroupAddresses(groupId);
     }
 
@@ -2651,9 +2652,9 @@
         }
     }
 
-    List<String> getDeviceAddresses(AudioDeviceAttributes device) {
+    List<String> getDeviceIdentityAddresses(AudioDeviceAttributes device) {
         synchronized (mDeviceStateLock) {
-            return mDeviceInventory.getDeviceAddresses(device);
+            return mDeviceInventory.getDeviceIdentityAddresses(device);
         }
     }
 
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index d138f24..e05824a 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -566,23 +566,40 @@
         final int mDeviceType;
         final @NonNull String mDeviceName;
         final @NonNull String mDeviceAddress;
+        @NonNull String mDeviceIdentityAddress;
         int mDeviceCodecFormat;
-        @NonNull String mPeerDeviceAddress;
         final int mGroupId;
+        @NonNull String mPeerDeviceAddress;
+        @NonNull String mPeerIdentityDeviceAddress;
 
         /** Disabled operating modes for this device. Use a negative logic so that by default
          * an empty list means all modes are allowed.
          * See BluetoothAdapter.AUDIO_MODE_DUPLEX and BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY */
         @NonNull ArraySet<String> mDisabledModes = new ArraySet(0);
 
-        DeviceInfo(int deviceType, String deviceName, String deviceAddress,
-                   int deviceCodecFormat, String peerDeviceAddress, int groupId) {
+        DeviceInfo(int deviceType, String deviceName, String address,
+                   String identityAddress, int codecFormat,
+                   int groupId, String peerAddress, String peerIdentityAddress) {
             mDeviceType = deviceType;
-            mDeviceName = deviceName == null ? "" : deviceName;
-            mDeviceAddress = deviceAddress == null ? "" : deviceAddress;
-            mDeviceCodecFormat = deviceCodecFormat;
-            mPeerDeviceAddress = peerDeviceAddress == null ? "" : peerDeviceAddress;
+            mDeviceName = TextUtils.emptyIfNull(deviceName);
+            mDeviceAddress = TextUtils.emptyIfNull(address);
+            mDeviceIdentityAddress = TextUtils.emptyIfNull(identityAddress);
+            mDeviceCodecFormat = codecFormat;
             mGroupId = groupId;
+            mPeerDeviceAddress = TextUtils.emptyIfNull(peerAddress);
+            mPeerIdentityDeviceAddress = TextUtils.emptyIfNull(peerIdentityAddress);
+        }
+
+        /** Constructor for all devices except A2DP sink and LE Audio */
+        DeviceInfo(int deviceType, String deviceName, String address) {
+            this(deviceType, deviceName, address, null, AudioSystem.AUDIO_FORMAT_DEFAULT);
+        }
+
+        /** Constructor for A2DP sink devices */
+        DeviceInfo(int deviceType, String deviceName, String address,
+                   String identityAddress, int codecFormat) {
+            this(deviceType, deviceName, address, identityAddress, codecFormat,
+                    BluetoothLeAudio.GROUP_ID_INVALID, null, null);
         }
 
         void setModeDisabled(String mode) {
@@ -601,25 +618,20 @@
             return isModeEnabled(BluetoothAdapter.AUDIO_MODE_DUPLEX);
         }
 
-        DeviceInfo(int deviceType, String deviceName, String deviceAddress,
-                   int deviceCodecFormat) {
-            this(deviceType, deviceName, deviceAddress, deviceCodecFormat,
-                    null, BluetoothLeAudio.GROUP_ID_INVALID);
-        }
-
-        DeviceInfo(int deviceType, String deviceName, String deviceAddress) {
-            this(deviceType, deviceName, deviceAddress, AudioSystem.AUDIO_FORMAT_DEFAULT);
-        }
-
         @Override
         public String toString() {
             return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType)
                     + " (" + AudioSystem.getDeviceName(mDeviceType)
                     + ") name:" + mDeviceName
                     + " addr:" + Utils.anonymizeBluetoothAddress(mDeviceType, mDeviceAddress)
+                    + " identity addr:"
+                    + Utils.anonymizeBluetoothAddress(mDeviceType, mDeviceIdentityAddress)
                     + " codec: " + Integer.toHexString(mDeviceCodecFormat)
-                    + " peer addr:" + mPeerDeviceAddress
                     + " group:" + mGroupId
+                    + " peer addr:"
+                    + Utils.anonymizeBluetoothAddress(mDeviceType, mPeerDeviceAddress)
+                    + " peer identity addr:"
+                    + Utils.anonymizeBluetoothAddress(mDeviceType, mPeerIdentityDeviceAddress)
                     + " disabled modes: " + mDisabledModes + "]";
         }
 
@@ -947,20 +959,44 @@
     }
 
 
+    /**
+     * Goes over all connected LE Audio devices in the provided group ID and
+     * update:
+     * - the peer address according to the addres of other device in the same
+     * group (can also clear the peer address is not anymore in the group)
+     * - The dentity address if not yet set.
+     * LE Audio buds in a pair are in the same group.
+     * @param groupId the LE Audio group to update
+     */
     /*package*/ void onUpdateLeAudioGroupAddresses(int groupId) {
         synchronized (mDevicesLock) {
+            // <address, identy address>
+            List<Pair<String, String>> addresses = new ArrayList<>();
             for (DeviceInfo di : mConnectedDevices.values()) {
                 if (di.mGroupId == groupId) {
-                    List<String> addresses = mDeviceBroker.getLeAudioGroupAddresses(groupId);
+                    if (addresses.isEmpty()) {
+                        addresses = mDeviceBroker.getLeAudioGroupAddresses(groupId);
+                    }
                     if (di.mPeerDeviceAddress.equals("")) {
-                        for (String addr : addresses) {
-                            if (!addr.equals(di.mDeviceAddress)) {
-                                di.mPeerDeviceAddress = addr;
+                        for (Pair<String, String> addr : addresses) {
+                            if (!addr.first.equals(di.mDeviceAddress)) {
+                                di.mPeerDeviceAddress = addr.first;
+                                di.mPeerIdentityDeviceAddress = addr.second;
                                 break;
                             }
                         }
-                    } else if (!addresses.contains(di.mPeerDeviceAddress)) {
+                    } else if (!addresses.contains(
+                            new Pair(di.mPeerDeviceAddress, di.mPeerIdentityDeviceAddress))) {
                         di.mPeerDeviceAddress = "";
+                        di.mPeerIdentityDeviceAddress = "";
+                    }
+                    if (di.mDeviceIdentityAddress.equals("")) {
+                        for (Pair<String, String> addr : addresses) {
+                            if (addr.first.equals(di.mDeviceAddress)) {
+                                di.mDeviceIdentityAddress = addr.second;
+                                break;
+                            }
+                        }
                     }
                 }
             }
@@ -1964,7 +2000,7 @@
         mDeviceBroker.clearA2dpSuspended(true /* internalOnly */);
 
         final DeviceInfo di = new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
-                address, codec);
+                address, btInfo.mDevice.getIdentityAddress(), codec);
         final String diKey = di.getKey();
         mConnectedDevices.put(diKey, di);
         // on a connection always overwrite the device seen by AudioPolicy, see comment above when
@@ -2381,12 +2417,15 @@
             // Find LE Group ID and peer headset address if available
             final int groupId = mDeviceBroker.getLeAudioDeviceGroupId(btInfo.mDevice);
             String peerAddress = "";
+            String peerIdentityAddress = "";
             if (groupId != BluetoothLeAudio.GROUP_ID_INVALID) {
-                List<String> addresses = mDeviceBroker.getLeAudioGroupAddresses(groupId);
+                List<Pair<String, String>> addresses =
+                        mDeviceBroker.getLeAudioGroupAddresses(groupId);
                 if (addresses.size() > 1) {
-                    for (String addr : addresses) {
-                        if (!addr.equals(address)) {
-                            peerAddress = addr;
+                    for (Pair<String, String> addr : addresses) {
+                        if (!addr.first.equals(address)) {
+                            peerAddress = addr.first;
+                            peerIdentityAddress = addr.second;
                             break;
                         }
                     }
@@ -2420,8 +2459,9 @@
             // Reset LEA suspend state each time a new sink is connected
             mDeviceBroker.clearLeAudioSuspended(true /* internalOnly */);
             mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address),
-                    new DeviceInfo(device, name, address, codec,
-                            peerAddress, groupId));
+                    new DeviceInfo(device, name, address,
+                            btInfo.mDevice.getIdentityAddress(), codec,
+                            groupId, peerAddress, peerIdentityAddress));
             mDeviceBroker.postAccessoryPlugMediaUnmute(device);
             setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false);
             addAudioDeviceInInventoryIfNeeded(device, address, peerAddress,
@@ -2806,18 +2846,19 @@
         mDevRoleCapturePresetDispatchers.finishBroadcast();
     }
 
-    List<String> getDeviceAddresses(AudioDeviceAttributes device) {
+    List<String> getDeviceIdentityAddresses(AudioDeviceAttributes device) {
         List<String> addresses = new ArrayList<String>();
         final String key = DeviceInfo.makeDeviceListKey(device.getInternalType(),
                 device.getAddress());
         synchronized (mDevicesLock) {
             DeviceInfo di = mConnectedDevices.get(key);
             if (di != null) {
-                if (!di.mDeviceAddress.isEmpty()) {
-                    addresses.add(di.mDeviceAddress);
+                if (!di.mDeviceIdentityAddress.isEmpty()) {
+                    addresses.add(di.mDeviceIdentityAddress);
                 }
-                if (!di.mPeerDeviceAddress.isEmpty()) {
-                    addresses.add(di.mPeerDeviceAddress);
+                if (!di.mPeerIdentityDeviceAddress.isEmpty()
+                        && !di.mPeerIdentityDeviceAddress.equals(di.mDeviceIdentityAddress)) {
+                    addresses.add(di.mPeerIdentityDeviceAddress);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0c78231..56ae2bf 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -13775,8 +13775,8 @@
         return activeAssistantUids;
     }
 
-    List<String> getDeviceAddresses(AudioDeviceAttributes device) {
-        return mDeviceBroker.getDeviceAddresses(device);
+    List<String> getDeviceIdentityAddresses(AudioDeviceAttributes device) {
+        return mDeviceBroker.getDeviceIdentityAddresses(device);
     }
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 8075618..a818c30 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -58,6 +58,7 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.Pair;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.utils.EventLogger;
@@ -1077,8 +1078,8 @@
         return mLeAudio.getGroupId(device);
     }
 
-    /*package*/ List<String> getLeAudioGroupAddresses(int groupId) {
-        List<String> addresses = new ArrayList<String>();
+    /*package*/ List<Pair<String, String>> getLeAudioGroupAddresses(int groupId) {
+        List<Pair<String, String>> addresses = new ArrayList<>();
         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
         if (adapter == null || mLeAudio == null) {
             return addresses;
@@ -1086,7 +1087,7 @@
         List<BluetoothDevice> activeDevices = adapter.getActiveDevices(BluetoothProfile.LE_AUDIO);
         for (BluetoothDevice device : activeDevices) {
             if (device != null && mLeAudio.getGroupId(device) == groupId) {
-                addresses.add(device.getAddress());
+                addresses.add(new Pair(device.getAddress(), device.getIdentityAddress()));
             }
         }
         return addresses;
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 4f7f31d..8428f12 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -1639,8 +1639,7 @@
             return  -1;
         }
         final AudioDeviceAttributes currentDevice = sRoutingDevices.get(0);
-        List<String> deviceAddresses = mAudioService.getDeviceAddresses(currentDevice);
-
+        List<String> deviceAddresses = mAudioService.getDeviceIdentityAddresses(currentDevice);
         // We limit only to Sensor.TYPE_HEAD_TRACKER here to avoid confusion
         // with gaming sensors. (Note that Sensor.TYPE_ROTATION_VECTOR
         // and Sensor.TYPE_GAME_ROTATION_VECTOR are supported internally by
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index dafea9a..d5d8fd2 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -51,9 +51,13 @@
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.biometrics.SensorLocationInternal;
 import android.hardware.biometrics.SensorPropertiesInternal;
+import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.face.FaceSensorConfigurations;
 import android.hardware.face.FaceSensorProperties;
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.face.IFaceService;
+import android.hardware.fingerprint.FingerprintSensorConfigurations;
 import android.hardware.fingerprint.FingerprintSensorProperties;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintService;
@@ -122,8 +126,6 @@
 
         /**
          * Allows to test with various device sensor configurations.
-         * @param context
-         * @return
          */
         @VisibleForTesting
         public String[] getConfiguration(Context context) {
@@ -131,6 +133,30 @@
         }
 
         /**
+         * Allows to test with various device sensor configurations.
+         */
+        @VisibleForTesting
+        public String[] getFingerprintConfiguration(Context context) {
+            return getConfiguration(context);
+        }
+
+        /**
+         * Allows to test with various device sensor configurations.
+         */
+        @VisibleForTesting
+        public String[] getFaceConfiguration(Context context) {
+            return getConfiguration(context);
+        }
+
+        /**
+         * Allows to test with various device sensor configurations.
+         */
+        @VisibleForTesting
+        public String[] getIrisConfiguration(Context context) {
+            return getConfiguration(context);
+        }
+
+        /**
          * Allows us to mock FingerprintService for testing
          */
         @VisibleForTesting
@@ -173,6 +199,22 @@
             }
             return false;
         }
+
+        /**
+         * Allows to test with various fingerprint aidl instances.
+         */
+        @VisibleForTesting
+        public String[] getFingerprintAidlInstances() {
+            return ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR);
+        }
+
+        /**
+         * Allows to test with various face aidl instances.
+         */
+        @VisibleForTesting
+        public String[] getFaceAidlInstances() {
+            return ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR);
+        }
     }
 
     private final class AuthServiceImpl extends IAuthService.Stub {
@@ -717,12 +759,129 @@
             hidlConfigs = null;
         }
 
-        // Registers HIDL and AIDL authenticators, but only HIDL configs need to be provided.
-        registerAuthenticators(hidlConfigs);
+        if (com.android.server.biometrics.Flags.deHidl()) {
+            Slog.d(TAG, "deHidl flag is on.");
+            registerAuthenticators();
+        } else {
+            // Registers HIDL and AIDL authenticators, but only HIDL configs need to be provided.
+            registerAuthenticators(hidlConfigs);
+        }
 
         mInjector.publishBinderService(this, mImpl);
     }
 
+    private void registerAuthenticators() {
+        registerFingerprintSensors(mInjector.getFingerprintAidlInstances(),
+                mInjector.getFingerprintConfiguration(getContext()));
+        registerFaceSensors(mInjector.getFaceAidlInstances(),
+                mInjector.getFaceConfiguration(getContext()));
+        registerIrisSensors(mInjector.getIrisConfiguration(getContext()));
+    }
+
+    private void registerIrisSensors(String[] hidlConfigStrings) {
+        final SensorConfig[] hidlConfigs;
+        if (!mInjector.isHidlDisabled(getContext())) {
+            final int firstApiLevel = SystemProperties.getInt(SYSPROP_FIRST_API_LEVEL, 0);
+            final int apiLevel = SystemProperties.getInt(SYSPROP_API_LEVEL, firstApiLevel);
+            if (hidlConfigStrings.length == 0 && apiLevel == Build.VERSION_CODES.R) {
+                // For backwards compatibility with R where biometrics could work without being
+                // configured in config_biometric_sensors. In the absence of a vendor provided
+                // configuration, we assume the weakest biometric strength (i.e. convenience).
+                Slog.w(TAG, "Found R vendor partition without config_biometric_sensors");
+                hidlConfigStrings = generateRSdkCompatibleConfiguration();
+            }
+            hidlConfigs = new SensorConfig[hidlConfigStrings.length];
+            for (int i = 0; i < hidlConfigStrings.length; ++i) {
+                hidlConfigs[i] = new SensorConfig(hidlConfigStrings[i]);
+            }
+        } else {
+            hidlConfigs = null;
+        }
+
+        final List<SensorPropertiesInternal> hidlIrisSensors = new ArrayList<>();
+
+        if (hidlConfigs != null) {
+            for (SensorConfig sensor : hidlConfigs) {
+                switch (sensor.modality) {
+                    case TYPE_IRIS:
+                        hidlIrisSensors.add(getHidlIrisSensorProps(sensor.id, sensor.strength));
+                        break;
+
+                    default:
+                        Slog.e(TAG, "Unknown modality: " + sensor.modality);
+                }
+            }
+        }
+
+        final IIrisService irisService = mInjector.getIrisService();
+        if (irisService != null) {
+            try {
+                irisService.registerAuthenticators(hidlIrisSensors);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "RemoteException when registering iris authenticators", e);
+            }
+        } else if (hidlIrisSensors.size() > 0) {
+            Slog.e(TAG, "HIDL iris configuration exists, but IrisService is null.");
+        }
+    }
+
+    private void registerFaceSensors(final String[] faceAidlInstances,
+            final String[] hidlConfigStrings) {
+        final FaceSensorConfigurations mFaceSensorConfigurations =
+                new FaceSensorConfigurations(hidlConfigStrings != null
+                        && hidlConfigStrings.length > 0);
+
+        if (hidlConfigStrings != null && hidlConfigStrings.length > 0) {
+            mFaceSensorConfigurations.addHidlConfigs(
+                    hidlConfigStrings, getContext());
+        }
+
+        if (faceAidlInstances != null && faceAidlInstances.length > 0) {
+            mFaceSensorConfigurations.addAidlConfigs(faceAidlInstances,
+                    name -> IFace.Stub.asInterface(Binder.allowBlocking(
+                            ServiceManager.waitForDeclaredService(name))));
+        }
+
+        final IFaceService faceService = mInjector.getFaceService();
+        if (faceService != null) {
+            try {
+                faceService.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "RemoteException when registering face authenticators", e);
+            }
+        }  else if (mFaceSensorConfigurations.hasSensorConfigurations()) {
+            Slog.e(TAG, "Face configuration exists, but FaceService is null.");
+        }
+    }
+
+    private void registerFingerprintSensors(final String[] fingerprintAidlInstances,
+            final String[] hidlConfigStrings) {
+        final FingerprintSensorConfigurations mFingerprintSensorConfigurations =
+                new FingerprintSensorConfigurations(!(hidlConfigStrings != null
+                        && hidlConfigStrings.length > 0));
+
+        if (hidlConfigStrings != null && hidlConfigStrings.length > 0) {
+            mFingerprintSensorConfigurations.addHidlSensors(hidlConfigStrings, getContext());
+        }
+
+        if (fingerprintAidlInstances != null && fingerprintAidlInstances.length > 0) {
+            mFingerprintSensorConfigurations.addAidlSensors(fingerprintAidlInstances,
+                    name -> IFingerprint.Stub.asInterface(Binder.allowBlocking(
+                            ServiceManager.waitForDeclaredService(name))));
+        }
+
+        final IFingerprintService fingerprintService = mInjector.getFingerprintService();
+        if (fingerprintService != null) {
+            try {
+                fingerprintService.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "RemoteException when registering fingerprint authenticators", e);
+            }
+        }  else if (mFingerprintSensorConfigurations.hasSensorConfigurations()) {
+            Slog.e(TAG, "Fingerprint configuration exists, but FingerprintService is null.");
+        }
+    }
+
     /**
      * Generates an array of string configs with entries that correspond to the biometric features
      * declared on the device. Returns an empty array if no biometric features are declared.
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index 037ea38a..89b638b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -202,7 +202,7 @@
     };
 
     @VisibleForTesting
-    BiometricScheduler(@NonNull String tag,
+    public BiometricScheduler(@NonNull String tag,
             @NonNull Handler handler,
             @SensorType int sensorType,
             @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
index 57feedc..0c3dfa7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
@@ -387,6 +387,11 @@
         return isAuthentication || isDetection;
     }
 
+    /** If this operation is {@link StartUserClient}. */
+    public boolean isStartUserOperation() {
+        return mClientMonitor instanceof StartUserClient<?, ?>;
+    }
+
     /** If this operation performs acquisition {@link AcquisitionClient}. */
     public boolean isAcquisitionOperation() {
         return mClientMonitor instanceof AcquisitionClient;
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
index 7f8f38f..6daaad1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
@@ -54,8 +54,6 @@
         // is all done internally.
         super(context, lazyDaemon, token, null /* ClientMonitorCallbackConverter */, userId, owner,
                 0 /* cookie */, sensorId, logger, biometricContext);
-        //, BiometricsProtoEnums.ACTION_ENUMERATE,
-          //      BiometricsProtoEnums.CLIENT_UNKNOWN);
         mEnrolledList = enrolledList;
         mUtils = utils;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
index 8075470..3753bbd 100644
--- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
@@ -135,7 +135,7 @@
         final int currentUserId = mCurrentUserRetriever.getCurrentUserId();
         final int nextUserId = mPendingOperations.getFirst().getTargetUserId();
 
-        if (nextUserId == currentUserId) {
+        if (nextUserId == currentUserId || mPendingOperations.getFirst().isStartUserOperation()) {
             super.startNextOperationIfIdle();
         } else if (currentUserId == UserHandle.USER_NULL) {
             final BaseClientMonitor startClient =
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 578d9dc..6af223b 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
@@ -36,6 +36,7 @@
 import android.hardware.biometrics.face.SensorProps;
 import android.hardware.face.Face;
 import android.hardware.face.FaceAuthenticateOptions;
+import android.hardware.face.FaceSensorConfigurations;
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.face.FaceServiceReceiver;
 import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
@@ -55,6 +56,7 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.Surface;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.SystemService;
@@ -76,6 +78,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.function.Supplier;
 
 /**
  * A service to manage multiple clients that want to access the face HAL API.
@@ -86,7 +89,7 @@
 
     protected static final String TAG = "FaceService";
 
-    private final FaceServiceWrapper mServiceWrapper;
+    @VisibleForTesting final FaceServiceWrapper mServiceWrapper;
     private final LockoutResetDispatcher mLockoutResetDispatcher;
     private final LockPatternUtils mLockPatternUtils;
     @NonNull
@@ -94,11 +97,18 @@
     @NonNull
     private final BiometricStateCallback<ServiceProvider, FaceSensorPropertiesInternal>
             mBiometricStateCallback;
+    @NonNull
+    private final FaceProviderFunction mFaceProviderFunction;
+
+    interface FaceProviderFunction {
+        FaceProvider getFaceProvider(Pair<String, SensorProps[]> filteredSensorProps,
+                boolean resetLockoutRequiresChallenge);
+    }
 
     /**
      * Receives the incoming binder calls from FaceManager.
      */
-    private final class FaceServiceWrapper extends IFaceService.Stub {
+    @VisibleForTesting final class FaceServiceWrapper extends IFaceService.Stub {
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
@@ -672,7 +682,8 @@
                     final SensorProps[] props = face.getSensorProps();
                     final FaceProvider provider = new FaceProvider(getContext(),
                             mBiometricStateCallback, props, instance, mLockoutResetDispatcher,
-                            BiometricContext.getInstance(getContext()));
+                            BiometricContext.getInstance(getContext()),
+                            false /* resetLockoutRequiresChallenge */);
                     providers.add(provider);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
@@ -704,6 +715,55 @@
             });
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+        public void registerAuthenticatorsLegacy(
+                FaceSensorConfigurations faceSensorConfigurations) {
+            super.registerAuthenticatorsLegacy_enforcePermission();
+
+            if (!faceSensorConfigurations.hasSensorConfigurations()) {
+                Slog.d(TAG, "No face sensors to register.");
+                return;
+            }
+            mRegistry.registerAll(() -> getProviders(faceSensorConfigurations));
+        }
+
+        private List<ServiceProvider> getProviders(
+                FaceSensorConfigurations faceSensorConfigurations) {
+            final List<ServiceProvider> providers = new ArrayList<>();
+            final Pair<String, SensorProps[]> filteredSensorProps =
+                    filterAvailableHalInstances(faceSensorConfigurations);
+            providers.add(mFaceProviderFunction.getFaceProvider(filteredSensorProps,
+                    faceSensorConfigurations.getResetLockoutRequiresChallenge()));
+            return providers;
+        }
+
+        @NonNull
+        private Pair<String, SensorProps[]> filterAvailableHalInstances(
+                FaceSensorConfigurations faceSensorConfigurations) {
+            Pair<String, SensorProps[]> finalSensorPair = faceSensorConfigurations.getSensorPair();
+
+            if (faceSensorConfigurations.isSingleSensorConfigurationPresent()) {
+                return finalSensorPair;
+            }
+
+            final Pair<String, SensorProps[]> virtualSensorProps = faceSensorConfigurations
+                    .getSensorPairForInstance("virtual");
+
+            if (Utils.isVirtualEnabled(getContext())) {
+                if (virtualSensorProps != null) {
+                    return virtualSensorProps;
+                } else {
+                    Slog.e(TAG, "Could not find virtual interface while it is enabled");
+                    return finalSensorPair;
+                }
+            } else {
+                if (virtualSensorProps != null) {
+                    return faceSensorConfigurations.getSensorPairNotForInstance("virtual");
+                }
+            }
+            return finalSensorPair;
+        }
+
         private Pair<List<FaceSensorPropertiesInternal>, List<String>>
                 filterAvailableHalInstances(
                 @NonNull List<FaceSensorPropertiesInternal> hidlInstances,
@@ -752,20 +812,36 @@
     }
 
     public FaceService(Context context) {
+        this(context, null /* faceProviderFunction */, () -> IBiometricService.Stub.asInterface(
+                ServiceManager.getService(Context.BIOMETRIC_SERVICE)));
+    }
+
+    @VisibleForTesting FaceService(Context context, FaceProviderFunction faceProviderFunction,
+            Supplier<IBiometricService> biometricServiceSupplier) {
         super(context);
         mServiceWrapper = new FaceServiceWrapper();
         mLockoutResetDispatcher = new LockoutResetDispatcher(context);
         mLockPatternUtils = new LockPatternUtils(context);
         mBiometricStateCallback = new BiometricStateCallback<>(UserManager.get(context));
-        mRegistry = new FaceServiceRegistry(mServiceWrapper,
-                () -> IBiometricService.Stub.asInterface(
-                        ServiceManager.getService(Context.BIOMETRIC_SERVICE)));
+        mRegistry = new FaceServiceRegistry(mServiceWrapper, biometricServiceSupplier);
         mRegistry.addAllRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
             @Override
             public void onAllAuthenticatorsRegistered(List<FaceSensorPropertiesInternal> sensors) {
                 mBiometricStateCallback.start(mRegistry.getProviders());
             }
         });
+
+        if (Flags.deHidl()) {
+            mFaceProviderFunction = faceProviderFunction != null ? faceProviderFunction :
+                    ((filteredSensorProps, resetLockoutRequiresChallenge) -> new FaceProvider(
+                            getContext(), mBiometricStateCallback,
+                            filteredSensorProps.second,
+                            filteredSensorProps.first, mLockoutResetDispatcher,
+                            BiometricContext.getInstance(getContext()),
+                            resetLockoutRequiresChallenge));
+        } else {
+            mFaceProviderFunction = ((filteredSensorProps, resetLockoutRequiresChallenge) -> null);
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java
index e5d4a63..ef2be79 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java
@@ -24,7 +24,8 @@
  * the user changes.
  */
 public class LockoutHalImpl implements LockoutTracker {
-    private @LockoutMode int mCurrentUserLockoutMode;
+    @LockoutMode
+    private int mCurrentUserLockoutMode;
 
     @Override
     public int getLockoutModeForUser(int userId) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
index 57f5b34..098be21 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
@@ -27,6 +27,7 @@
 import android.hardware.keymaster.HardwareAuthToken;
 import android.util.Slog;
 
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.sensors.AcquisitionClient;
@@ -59,6 +60,20 @@
         void onHardwareUnavailable();
     }
 
+    /**
+     * Interface to send results to the AidlResponseHandler's owner.
+     */
+    public interface AidlResponseHandlerCallback {
+        /**
+         * Invoked when enrollment is successful.
+         */
+        void onEnrollSuccess();
+        /**
+         * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
+         */
+        void onHardwareUnavailable();
+    }
+
     private static final String TAG = "AidlResponseHandler";
 
     @NonNull
@@ -68,7 +83,7 @@
     private final int mSensorId;
     private final int mUserId;
     @NonNull
-    private final LockoutTracker mLockoutCache;
+    private final LockoutTracker mLockoutTracker;
     @NonNull
     private final LockoutResetDispatcher mLockoutResetDispatcher;
 
@@ -76,6 +91,8 @@
     private final AuthSessionCoordinator mAuthSessionCoordinator;
     @NonNull
     private final HardwareUnavailableCallback mHardwareUnavailableCallback;
+    @NonNull
+    private final AidlResponseHandlerCallback mAidlResponseHandlerCallback;
 
     public AidlResponseHandler(@NonNull Context context,
             @NonNull BiometricScheduler scheduler, int sensorId, int userId,
@@ -83,14 +100,33 @@
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull AuthSessionCoordinator authSessionCoordinator,
             @NonNull HardwareUnavailableCallback hardwareUnavailableCallback) {
+        this(context, scheduler, sensorId, userId, lockoutTracker, lockoutResetDispatcher,
+                authSessionCoordinator, hardwareUnavailableCallback,
+                new AidlResponseHandlerCallback() {
+                    @Override
+                    public void onEnrollSuccess() {}
+
+                    @Override
+                    public void onHardwareUnavailable() {}
+                });
+    }
+
+    public AidlResponseHandler(@NonNull Context context,
+            @NonNull BiometricScheduler scheduler, int sensorId, int userId,
+            @NonNull LockoutTracker lockoutTracker,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull AuthSessionCoordinator authSessionCoordinator,
+            @NonNull HardwareUnavailableCallback hardwareUnavailableCallback,
+            @NonNull AidlResponseHandlerCallback aidlResponseHandlerCallback) {
         mContext = context;
         mScheduler = scheduler;
         mSensorId = sensorId;
         mUserId = userId;
-        mLockoutCache = lockoutTracker;
+        mLockoutTracker = lockoutTracker;
         mLockoutResetDispatcher = lockoutResetDispatcher;
         mAuthSessionCoordinator = authSessionCoordinator;
         mHardwareUnavailableCallback = hardwareUnavailableCallback;
+        mAidlResponseHandlerCallback = aidlResponseHandlerCallback;
     }
 
     @Override
@@ -106,13 +142,13 @@
     @Override
     public void onChallengeGenerated(long challenge) {
         handleResponse(FaceGenerateChallengeClient.class, (c) -> c.onChallengeGenerated(mSensorId,
-                mUserId, challenge), null);
+                mUserId, challenge));
     }
 
     @Override
     public void onChallengeRevoked(long challenge) {
         handleResponse(FaceRevokeChallengeClient.class, (c) -> c.onChallengeRevoked(mSensorId,
-                mUserId, challenge), null);
+                mUserId, challenge));
     }
 
     @Override
@@ -123,7 +159,7 @@
                 return;
             }
             c.onAuthenticationFrame(AidlConversionUtils.toFrameworkAuthenticationFrame(frame));
-        }, null);
+        });
     }
 
     @Override
@@ -134,7 +170,7 @@
                 return;
             }
             c.onEnrollmentFrame(AidlConversionUtils.toFrameworkEnrollmentFrame(frame));
-        }, null);
+        });
     }
 
     @Override
@@ -149,9 +185,13 @@
         handleResponse(ErrorConsumer.class, (c) -> {
             c.onError(error, vendorCode);
             if (error == Error.HW_UNAVAILABLE) {
-                mHardwareUnavailableCallback.onHardwareUnavailable();
+                if (Flags.deHidl()) {
+                    mAidlResponseHandlerCallback.onHardwareUnavailable();
+                } else {
+                    mHardwareUnavailableCallback.onHardwareUnavailable();
+                }
             }
-        }, null);
+        });
     }
 
     @Override
@@ -167,7 +207,12 @@
                 .getUniqueName(mContext, currentUserId);
         final Face face = new Face(name, enrollmentId, mSensorId);
 
-        handleResponse(FaceEnrollClient.class, (c) -> c.onEnrollResult(face, remaining), null);
+        handleResponse(FaceEnrollClient.class, (c) -> {
+            c.onEnrollResult(face, remaining);
+            if (remaining == 0) {
+                mAidlResponseHandlerCallback.onEnrollSuccess();
+            }
+        });
     }
 
     @Override
@@ -179,37 +224,37 @@
             byteList.add(b);
         }
         handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(face,
-                true /* authenticated */, byteList), null);
+                true /* authenticated */, byteList));
     }
 
     @Override
     public void onAuthenticationFailed() {
         final Face face = new Face("" /* name */, 0 /* faceId */, mSensorId);
         handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(face,
-                false /* authenticated */, null /* hat */), null);
+                false /* authenticated */, null /* hat */));
     }
 
     @Override
     public void onLockoutTimed(long durationMillis) {
-        handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis), null);
+        handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis));
     }
 
     @Override
     public void onLockoutPermanent() {
-        handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent, null);
+        handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent);
     }
 
     @Override
     public void onLockoutCleared() {
         handleResponse(FaceResetLockoutClient.class, FaceResetLockoutClient::onLockoutCleared,
                 (c) -> FaceResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId,
-                mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
-                Utils.getCurrentStrength(mSensorId), -1 /* requestId */));
+                        mLockoutTracker, mLockoutResetDispatcher, mAuthSessionCoordinator,
+                        Utils.getCurrentStrength(mSensorId), -1 /* requestId */));
     }
 
     @Override
     public void onInteractionDetected() {
-        handleResponse(FaceDetectClient.class, FaceDetectClient::onInteractionDetected, null);
+        handleResponse(FaceDetectClient.class, FaceDetectClient::onInteractionDetected);
     }
 
     @Override
@@ -219,23 +264,23 @@
                 final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId);
                 final int finalI = i;
                 handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(face,
-                        enrollmentIds.length - finalI - 1), null);
+                        enrollmentIds.length - finalI - 1 /* remaining */));
             }
         } else {
             handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(
-                    null /* identifier */, 0 /* remaining */), null);
+                    null /* identifier */, 0 /* remaining */));
         }
     }
 
     @Override
     public void onFeaturesRetrieved(byte[] features) {
         handleResponse(FaceGetFeatureClient.class, (c) -> c.onFeatureGet(true /* success */,
-                features), null);
+                features));
     }
 
     @Override
     public void onFeatureSet(byte feature) {
-        handleResponse(FaceSetFeatureClient.class, (c) -> c.onFeatureSet(true /* success */), null);
+        handleResponse(FaceSetFeatureClient.class, (c) -> c.onFeatureSet(true /* success */));
     }
 
     @Override
@@ -245,33 +290,32 @@
                 final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId);
                 final int finalI = i;
                 handleResponse(RemovalConsumer.class,
-                        (c) -> c.onRemoved(face, enrollmentIds.length - finalI - 1),
-                        null);
+                        (c) -> c.onRemoved(face,
+                                enrollmentIds.length - finalI - 1 /* remaining */));
             }
         } else {
             handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(null /* identifier */,
-                    0 /* remaining */), null);
+                    0 /* remaining */));
         }
     }
 
     @Override
     public void onAuthenticatorIdRetrieved(long authenticatorId) {
         handleResponse(FaceGetAuthenticatorIdClient.class, (c) -> c.onAuthenticatorIdRetrieved(
-                authenticatorId), null);
+                authenticatorId));
     }
 
     @Override
     public void onAuthenticatorIdInvalidated(long newAuthenticatorId) {
         handleResponse(FaceInvalidationClient.class, (c) -> c.onAuthenticatorIdInvalidated(
-                newAuthenticatorId), null);
+                newAuthenticatorId));
     }
 
     /**
      * Handles acquired messages sent by the HAL (specifically for HIDL HAL).
      */
     public void onAcquired(int acquiredInfo, int vendorCode) {
-        handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode),
-                null);
+        handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode));
     }
 
     /**
@@ -288,7 +332,7 @@
                 lockoutMode = LockoutTracker.LOCKOUT_TIMED;
             }
 
-            mLockoutCache.setLockoutModeForUser(mUserId, lockoutMode);
+            mLockoutTracker.setLockoutModeForUser(mUserId, lockoutMode);
 
             if (duration == 0) {
                 mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorId);
@@ -296,6 +340,20 @@
         });
     }
 
+    /**
+     * Handle clients which are not supported in HIDL HAL. For face, FaceInvalidationClient
+     * is the only AIDL client which is not supported in HIDL.
+     */
+    public void onUnsupportedClientScheduled() {
+        Slog.e(TAG, "FaceInvalidationClient is not supported in the HAL.");
+        handleResponse(FaceInvalidationClient.class, BaseClientMonitor::cancel);
+    }
+
+    private <T> void handleResponse(@NonNull Class<T> className,
+            @NonNull Consumer<T> action) {
+        handleResponse(className, action, null /* alternateAction */);
+    }
+
     private <T> void handleResponse(@NonNull Class<T> className,
             @NonNull Consumer<T> actionIfClassMatchesClient,
             @Nullable Consumer<BaseClientMonitor> alternateAction) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
index e70e25a..af46f44 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
@@ -21,7 +21,7 @@
 import android.hardware.biometrics.face.ISession;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 
-import com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter;
+import com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter;
 
 import java.util.function.Supplier;
 
@@ -47,7 +47,7 @@
 
     public AidlSession(Context context, Supplier<IBiometricsFace> session, int userId,
             AidlResponseHandler aidlResponseHandler) {
-        mSession = new AidlToHidlAdapter(context, session, userId, aidlResponseHandler);
+        mSession = new HidlToAidlSessionAdapter(context, session, userId, aidlResponseHandler);
         mHalInterfaceVersion = 0;
         mUserId = userId;
         mAidlResponseHandler = aidlResponseHandler;
@@ -64,7 +64,7 @@
     }
 
     /** The HAL callback, which should only be used in tests {@See BiometricTestSessionImpl}. */
-    AidlResponseHandler getHalSessionCallback() {
+    public AidlResponseHandler getHalSessionCallback() {
         return mAidlResponseHandler;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
index c15049b..c41b706 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
@@ -34,7 +34,7 @@
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.ErrorConsumer;
 import com.android.server.biometrics.sensors.HalClientMonitor;
-import com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter;
+import com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -75,8 +75,8 @@
     protected void startHalOperation() {
         try {
             ISession session = getFreshDaemon().getSession();
-            if (session instanceof AidlToHidlAdapter) {
-                ((AidlToHidlAdapter) session).setFeature(mFeature);
+            if (session instanceof HidlToAidlSessionAdapter) {
+                ((HidlToAidlSessionAdapter) session).setFeature(mFeature);
             }
             session.getFeatures();
         } catch (RemoteException e) {
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 dd9c6d5..9fa15b8 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
@@ -24,6 +24,7 @@
 import android.app.TaskStackListener;
 import android.content.Context;
 import android.content.pm.UserInfo;
+import android.hardware.biometrics.BiometricFaceConstants;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.IInvalidationCallback;
@@ -51,6 +52,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
 import com.android.server.biometrics.AuthenticationStatsCollector;
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
@@ -64,11 +66,13 @@
 import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
 import com.android.server.biometrics.sensors.InvalidationRequesterClient;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.PerformanceTracker;
 import com.android.server.biometrics.sensors.SensorList;
 import com.android.server.biometrics.sensors.face.FaceUtils;
 import com.android.server.biometrics.sensors.face.ServiceProvider;
 import com.android.server.biometrics.sensors.face.UsageStats;
+import com.android.server.biometrics.sensors.face.hidl.HidlToAidlSensorAdapter;
 
 import org.json.JSONArray;
 import org.json.JSONException;
@@ -152,9 +156,11 @@
             @NonNull SensorProps[] props,
             @NonNull String halInstanceName,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull BiometricContext biometricContext) {
+            @NonNull BiometricContext biometricContext,
+            boolean resetLockoutRequiresChallenge) {
         this(context, biometricStateCallback, props, halInstanceName, lockoutResetDispatcher,
-                biometricContext, null /* daemon */);
+                biometricContext, null /* daemon */, resetLockoutRequiresChallenge,
+                false /* testHalEnabled */);
     }
 
     @VisibleForTesting FaceProvider(@NonNull Context context,
@@ -163,7 +169,8 @@
             @NonNull String halInstanceName,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull BiometricContext biometricContext,
-            IFace daemon) {
+            @Nullable IFace daemon, boolean resetLockoutRequiresChallenge,
+            boolean testHalEnabled) {
         mContext = context;
         mBiometricStateCallback = biometricStateCallback;
         mHalInstanceName = halInstanceName;
@@ -176,48 +183,118 @@
         mBiometricContext = biometricContext;
         mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
         mDaemon = daemon;
+        mTestHalEnabled = testHalEnabled;
 
-        AuthenticationStatsBroadcastReceiver mBroadcastReceiver =
-                new AuthenticationStatsBroadcastReceiver(
-                        mContext,
-                        BiometricsProtoEnums.MODALITY_FACE,
-                        (AuthenticationStatsCollector collector) -> {
-                            Slog.d(getTag(), "Initializing AuthenticationStatsCollector");
-                            mAuthenticationStatsCollector = collector;
-                        });
+        initAuthenticationBroadcastReceiver();
+        initSensors(resetLockoutRequiresChallenge, props);
+    }
 
-        for (SensorProps prop : props) {
-            final int sensorId = prop.commonProps.sensorId;
+    private void initAuthenticationBroadcastReceiver() {
+        new AuthenticationStatsBroadcastReceiver(
+                mContext,
+                BiometricsProtoEnums.MODALITY_FACE,
+                (AuthenticationStatsCollector collector) -> {
+                    Slog.d(getTag(), "Initializing AuthenticationStatsCollector");
+                    mAuthenticationStatsCollector = collector;
+                });
+    }
 
-            final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
-            if (prop.commonProps.componentInfo != null) {
-                for (ComponentInfo info : prop.commonProps.componentInfo) {
-                    componentInfo.add(new ComponentInfoInternal(info.componentId,
-                            info.hardwareVersion, info.firmwareVersion, info.serialNumber,
-                            info.softwareVersion));
+    private void initSensors(boolean resetLockoutRequiresChallenge, SensorProps[] props) {
+        if (Flags.deHidl()) {
+            if (resetLockoutRequiresChallenge) {
+                Slog.d(getTag(), "Adding HIDL configs");
+                for (SensorProps prop : props) {
+                    addHidlSensors(prop, resetLockoutRequiresChallenge);
+                }
+            } else {
+                Slog.d(getTag(), "Adding AIDL configs");
+                for (SensorProps prop : props) {
+                    addAidlSensors(prop, resetLockoutRequiresChallenge);
                 }
             }
+        } else {
+            for (SensorProps prop : props) {
+                final int sensorId = prop.commonProps.sensorId;
 
-            final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
-                    prop.commonProps.sensorId, prop.commonProps.sensorStrength,
-                    prop.commonProps.maxEnrollmentsPerUser, componentInfo, prop.sensorType,
-                    prop.supportsDetectInteraction, prop.halControlsPreview,
-                    false /* resetLockoutRequiresChallenge */);
-            final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
-                    internalProp, lockoutResetDispatcher, mBiometricContext);
-            final int userId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
-                    sensor.getLazySession().get().getUserId();
-            mFaceSensors.addSensor(sensorId, sensor, userId,
-                    new SynchronousUserSwitchObserver() {
-                        @Override
-                        public void onUserSwitching(int newUserId) {
-                            scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
-                        }
-                    });
-            Slog.d(getTag(), "Added: " + internalProp);
+                final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+                if (prop.commonProps.componentInfo != null) {
+                    for (ComponentInfo info : prop.commonProps.componentInfo) {
+                        componentInfo.add(new ComponentInfoInternal(info.componentId,
+                                info.hardwareVersion, info.firmwareVersion, info.serialNumber,
+                                info.softwareVersion));
+                    }
+                }
+
+                final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
+                        prop.commonProps.sensorId, prop.commonProps.sensorStrength,
+                        prop.commonProps.maxEnrollmentsPerUser, componentInfo, prop.sensorType,
+                        prop.supportsDetectInteraction, prop.halControlsPreview,
+                        false /* resetLockoutRequiresChallenge */);
+                final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this,
+                        mContext, mHandler, internalProp, mLockoutResetDispatcher,
+                        mBiometricContext);
+                sensor.init(mLockoutResetDispatcher, this);
+                final int userId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+                        sensor.getLazySession().get().getUserId();
+                mFaceSensors.addSensor(sensorId, sensor, userId,
+                        new SynchronousUserSwitchObserver() {
+                            @Override
+                            public void onUserSwitching(int newUserId) {
+                                scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+                            }
+                        });
+                Slog.d(getTag(), "Added: " + internalProp);
+            }
         }
     }
 
+    private void addHidlSensors(SensorProps prop, boolean resetLockoutRequiresChallenge) {
+        final int sensorId = prop.commonProps.sensorId;
+        final Sensor sensor = new HidlToAidlSensorAdapter(getTag() + "/" + sensorId, this,
+                mContext, mHandler, prop, mLockoutResetDispatcher,
+                mBiometricContext, resetLockoutRequiresChallenge,
+                () -> {
+                    //TODO: update to make this testable
+                    scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser(),
+                            null /* callback */);
+                    scheduleGetFeature(sensorId, new Binder(), ActivityManager.getCurrentUser(),
+                            BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION, null,
+                            mContext.getOpPackageName());
+                });
+        sensor.init(mLockoutResetDispatcher, this);
+        final int userId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+                sensor.getLazySession().get().getUserId();
+        mFaceSensors.addSensor(sensorId, sensor, userId,
+                new SynchronousUserSwitchObserver() {
+                    @Override
+                    public void onUserSwitching(int newUserId) {
+                        scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+                        scheduleGetFeature(sensorId, new Binder(), newUserId,
+                                BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION,
+                                null, mContext.getOpPackageName());
+                    }
+                });
+        Slog.d(getTag(), "Added: " + mFaceSensors.get(sensorId));
+    }
+
+    private void addAidlSensors(SensorProps prop, boolean resetLockoutRequiresChallenge) {
+        final int sensorId = prop.commonProps.sensorId;
+        final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext,
+                mHandler, prop, mLockoutResetDispatcher, mBiometricContext,
+                resetLockoutRequiresChallenge);
+        sensor.init(mLockoutResetDispatcher, this);
+        final int userId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+                sensor.getLazySession().get().getUserId();
+        mFaceSensors.addSensor(sensorId, sensor, userId,
+                new SynchronousUserSwitchObserver() {
+                    @Override
+                    public void onUserSwitching(int newUserId) {
+                        scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+                    }
+                });
+        Slog.d(getTag(), "Added: " + mFaceSensors.get(sensorId));
+    }
+
     private String getTag() {
         return "FaceProvider/" + mHalInstanceName;
     }
@@ -290,7 +367,10 @@
         }
     }
 
-    private void scheduleLoadAuthenticatorIdsForUser(int sensorId, int userId) {
+    /**
+     * Schedules FaceGetAuthenticatorIdClient for specific sensor and user.
+     */
+    protected void scheduleLoadAuthenticatorIdsForUser(int sensorId, int userId) {
         mHandler.post(() -> {
             final FaceGetAuthenticatorIdClient client = new FaceGetAuthenticatorIdClient(
                     mContext, mFaceSensors.get(sensorId).getLazySession(), userId,
@@ -365,8 +445,12 @@
 
     @Override
     public int getLockoutModeForUser(int sensorId, int userId) {
-        return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
-                Utils.getCurrentStrength(sensorId));
+        if (Flags.deHidl()) {
+            return mFaceSensors.get(sensorId).getLockoutModeForUser(userId);
+        } else {
+            return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
+                    Utils.getCurrentStrength(sensorId));
+        }
     }
 
     @Override
@@ -376,13 +460,18 @@
 
     @Override
     public boolean isHardwareDetected(int sensorId) {
-        return hasHalInstance();
+        if (Flags.deHidl()) {
+            return mFaceSensors.get(sensorId).isHardwareDetected(mHalInstanceName);
+        } else {
+            return hasHalInstance();
+        }
     }
 
     @Override
     public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,
             @NonNull IFaceServiceReceiver receiver, String opPackageName) {
         mHandler.post(() -> {
+            mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
             final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
                     mFaceSensors.get(sensorId).getLazySession(), token,
                     new ClientMonitorCallbackConverter(receiver), userId, opPackageName, sensorId,
@@ -416,6 +505,7 @@
             @Nullable Surface previewSurface, boolean debugConsent) {
         final long id = mRequestCounter.incrementAndGet();
         mHandler.post(() -> {
+            mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
             final int maxTemplatesPerUser = mFaceSensors.get(
                     sensorId).getSensorProperties().maxEnrollmentsPerUser;
             final FaceEnrollClient client = new FaceEnrollClient(mContext,
@@ -427,18 +517,23 @@
                             BiometricsProtoEnums.CLIENT_UNKNOWN,
                             mAuthenticationStatsCollector),
                     mBiometricContext, maxTemplatesPerUser, debugConsent);
-            scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
-                            mBiometricStateCallback, new ClientMonitorCallback() {
-                        @Override
-                        public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
-                                boolean success) {
-                            ClientMonitorCallback.super.onClientFinished(clientMonitor, success);
-                            if (success) {
-                                scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
-                                scheduleInvalidationRequest(sensorId, userId);
+            if (Flags.deHidl()) {
+                scheduleForSensor(sensorId, client, mBiometricStateCallback);
+            } else {
+                scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
+                        mBiometricStateCallback, new ClientMonitorCallback() {
+                            @Override
+                            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+                                    boolean success) {
+                                ClientMonitorCallback.super.onClientFinished(clientMonitor,
+                                        success);
+                                if (success) {
+                                    scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
+                                    scheduleInvalidationRequest(sensorId, userId);
+                                }
                             }
-                        }
-                    }));
+                        }));
+            }
         });
         return id;
     }
@@ -486,6 +581,13 @@
             final int userId = options.getUserId();
             final int sensorId = options.getSensorId();
             final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
+            mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
+            final LockoutTracker lockoutTracker;
+            if (Flags.deHidl()) {
+                lockoutTracker = mFaceSensors.get(sensorId).getLockoutTracker(true /* forAuth */);
+            } else {
+                lockoutTracker = null;
+            }
             final FaceAuthenticationClient client = new FaceAuthenticationClient(
                     mContext, mFaceSensors.get(sensorId).getLazySession(), token, requestId,
                     callback, operationId, restricted, options, cookie,
@@ -493,7 +595,7 @@
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
                             mAuthenticationStatsCollector),
                     mBiometricContext, isStrongBiometric,
-                    mUsageStats, null /* lockoutTracker */,
+                    mUsageStats, lockoutTracker,
                     allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId));
             scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
                 @Override
@@ -555,6 +657,7 @@
     private void scheduleRemoveSpecifiedIds(int sensorId, @NonNull IBinder token, int[] faceIds,
             int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
         mHandler.post(() -> {
+            mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
             final FaceRemovalClient client = new FaceRemovalClient(mContext,
                     mFaceSensors.get(sensorId).getLazySession(), token,
                     new ClientMonitorCallbackConverter(receiver), faceIds, userId,
@@ -571,6 +674,7 @@
     @Override
     public void scheduleResetLockout(int sensorId, int userId, @NonNull byte[] hardwareAuthToken) {
         mHandler.post(() -> {
+            mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
             final FaceResetLockoutClient client = new FaceResetLockoutClient(
                     mContext, mFaceSensors.get(sensorId).getLazySession(), userId,
                     mContext.getOpPackageName(), sensorId,
@@ -578,8 +682,8 @@
                             BiometricsProtoEnums.CLIENT_UNKNOWN,
                             mAuthenticationStatsCollector),
                     mBiometricContext, hardwareAuthToken,
-                    mFaceSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher,
-                    Utils.getCurrentStrength(sensorId));
+                    mFaceSensors.get(sensorId).getLockoutTracker(false/* forAuth */),
+                    mLockoutResetDispatcher, Utils.getCurrentStrength(sensorId));
 
             scheduleForSensor(sensorId, client);
         });
@@ -590,6 +694,7 @@
             boolean enabled, @NonNull byte[] hardwareAuthToken,
             @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
         mHandler.post(() -> {
+            mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
             final List<Face> faces = FaceUtils.getInstance(sensorId)
                     .getBiometricsForUser(mContext, userId);
             if (faces.isEmpty()) {
@@ -610,6 +715,7 @@
     public void scheduleGetFeature(int sensorId, @NonNull IBinder token, int userId, int feature,
             @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName) {
         mHandler.post(() -> {
+            mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
             final List<Face> faces = FaceUtils.getInstance(sensorId)
                     .getBiometricsForUser(mContext, userId);
             if (faces.isEmpty()) {
@@ -641,6 +747,7 @@
     public void scheduleInternalCleanup(int sensorId, int userId,
             @Nullable ClientMonitorCallback callback, boolean favorHalEnrollments) {
         mHandler.post(() -> {
+            mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
             final FaceInternalCleanupClient client =
                     new FaceInternalCleanupClient(mContext,
                             mFaceSensors.get(sensorId).getLazySession(), userId,
@@ -760,4 +867,8 @@
         }
         biometricScheduler.startWatchdog();
     }
+
+    public boolean getTestHalEnabled() {
+        return mTestHalEnabled;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
index 77b5592..d02eefa 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.hardware.biometrics.BiometricManager.Authenticators;
 import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.face.ISession;
 import android.hardware.keymaster.HardwareAuthToken;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -34,6 +35,7 @@
 import com.android.server.biometrics.sensors.HalClientMonitor;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter;
 
 import java.util.function.Supplier;
 
@@ -47,7 +49,7 @@
     private static final String TAG = "FaceResetLockoutClient";
 
     private final HardwareAuthToken mHardwareAuthToken;
-    private final LockoutTracker mLockoutCache;
+    private final LockoutTracker mLockoutTracker;
     private final LockoutResetDispatcher mLockoutResetDispatcher;
     private final int mBiometricStrength;
 
@@ -60,7 +62,7 @@
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
                 0 /* cookie */, sensorId, logger, biometricContext);
         mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
-        mLockoutCache = lockoutTracker;
+        mLockoutTracker = lockoutTracker;
         mLockoutResetDispatcher = lockoutResetDispatcher;
         mBiometricStrength = biometricStrength;
     }
@@ -79,7 +81,11 @@
     @Override
     protected void startHalOperation() {
         try {
-            getFreshDaemon().getSession().resetLockout(mHardwareAuthToken);
+            final ISession session = getFreshDaemon().getSession();
+            session.resetLockout(mHardwareAuthToken);
+            if (session instanceof HidlToAidlSessionAdapter) {
+                mCallback.onClientFinished(this, true /* success */);
+            }
         } catch (RemoteException e) {
             Slog.e(TAG, "Unable to reset lockout", e);
             mCallback.onClientFinished(this, false /* success */);
@@ -87,7 +93,7 @@
     }
 
     void onLockoutCleared() {
-        resetLocalLockoutStateToNone(getSensorId(), getTargetUserId(), mLockoutCache,
+        resetLocalLockoutStateToNone(getSensorId(), getTargetUserId(), mLockoutTracker,
                 mLockoutResetDispatcher, getBiometricContext().getAuthSessionCoordinator(),
                 mBiometricStrength, getRequestId());
         mCallback.onClientFinished(this, true /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index 54e66eb..3e5c599 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -21,16 +21,20 @@
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.ITestSession;
 import android.hardware.biometrics.ITestSessionCallback;
+import android.hardware.biometrics.common.ComponentInfo;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.ISession;
+import android.hardware.biometrics.face.SensorProps;
 import android.hardware.face.FaceManager;
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Slog;
@@ -38,6 +42,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.SensorServiceStateProto;
 import com.android.server.biometrics.SensorStateProto;
 import com.android.server.biometrics.UserStateProto;
@@ -49,12 +54,15 @@
 import com.android.server.biometrics.sensors.ErrorConsumer;
 import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.StartUserClient;
 import com.android.server.biometrics.sensors.StopUserClient;
 import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
 import com.android.server.biometrics.sensors.face.FaceUtils;
 
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.function.Supplier;
 
@@ -71,25 +79,53 @@
     @NonNull private final IBinder mToken;
     @NonNull private final Handler mHandler;
     @NonNull private final FaceSensorPropertiesInternal mSensorProperties;
-    @NonNull private final UserAwareBiometricScheduler mScheduler;
-    @NonNull private final LockoutCache mLockoutCache;
+    @NonNull private BiometricScheduler mScheduler;
+    @Nullable private LockoutTracker mLockoutTracker;
     @NonNull private final Map<Integer, Long> mAuthenticatorIds;
 
-    @NonNull private final Supplier<AidlSession> mLazySession;
+    @NonNull private Supplier<AidlSession> mLazySession;
     @Nullable AidlSession mCurrentSession;
+    @NonNull BiometricContext mBiometricContext;
 
 
     Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
             @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull BiometricContext biometricContext, AidlSession session) {
+            @NonNull BiometricContext biometricContext, @Nullable AidlSession session) {
         mTag = tag;
         mProvider = provider;
         mContext = context;
         mToken = new Binder();
         mHandler = handler;
         mSensorProperties = sensorProperties;
-        mScheduler = new UserAwareBiometricScheduler(tag,
+        mBiometricContext = biometricContext;
+        mAuthenticatorIds = new HashMap<>();
+    }
+
+    Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
+            @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull BiometricContext biometricContext) {
+        this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher,
+                biometricContext, null);
+    }
+
+    public Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
+            @NonNull Handler handler, @NonNull SensorProps prop,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull BiometricContext biometricContext,
+            boolean resetLockoutRequiresChallenge) {
+        this(tag, provider, context, handler,
+                getFaceSensorPropertiesInternal(prop, resetLockoutRequiresChallenge),
+                lockoutResetDispatcher, biometricContext, null);
+    }
+
+    /**
+     * Initialize biometric scheduler, lockout tracker and session for the sensor.
+     */
+    public void init(LockoutResetDispatcher lockoutResetDispatcher,
+            FaceProvider provider) {
+        mScheduler = new UserAwareBiometricScheduler(mTag,
                 BiometricScheduler.SENSOR_TYPE_FACE, null /* gestureAvailabilityDispatcher */,
                 () -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL,
                 new UserAwareBiometricScheduler.UserSwitchCallback() {
@@ -98,7 +134,7 @@
                     public StopUserClient<?> getStopUserClient(int userId) {
                         return new FaceStopUserClient(mContext, mLazySession, mToken, userId,
                                 mSensorProperties.sensorId,
-                                BiometricLogger.ofUnknown(mContext), biometricContext,
+                                BiometricLogger.ofUnknown(mContext), mBiometricContext,
                                 () -> mCurrentSession = null);
                     }
 
@@ -107,13 +143,36 @@
                     public StartUserClient<?, ?> getStartUserClient(int newUserId) {
                         final int sensorId = mSensorProperties.sensorId;
 
-                        final AidlResponseHandler resultController = new AidlResponseHandler(
-                                mContext, mScheduler, sensorId, newUserId,
-                                mLockoutCache, lockoutResetDispatcher,
-                                biometricContext.getAuthSessionCoordinator(), () -> {
-                            Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
-                            mCurrentSession = null;
-                        });
+                        final AidlResponseHandler resultController;
+                        if (Flags.deHidl()) {
+                            resultController = new AidlResponseHandler(
+                                    mContext, mScheduler, sensorId, newUserId,
+                                    mLockoutTracker, lockoutResetDispatcher,
+                                    mBiometricContext.getAuthSessionCoordinator(), () -> {},
+                                    new AidlResponseHandler.AidlResponseHandlerCallback() {
+                                        @Override
+                                        public void onEnrollSuccess() {
+                                            mProvider.scheduleLoadAuthenticatorIdsForUser(sensorId,
+                                                    newUserId);
+                                            mProvider.scheduleInvalidationRequest(sensorId,
+                                                    newUserId);
+                                        }
+
+                                        @Override
+                                        public void onHardwareUnavailable() {
+                                            Slog.e(mTag, "Face sensor hardware unavailable.");
+                                            mCurrentSession = null;
+                                        }
+                                    });
+                        } else {
+                            resultController = new AidlResponseHandler(
+                                    mContext, mScheduler, sensorId, newUserId,
+                                    mLockoutTracker, lockoutResetDispatcher,
+                                    mBiometricContext.getAuthSessionCoordinator(), () -> {
+                                Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
+                                mCurrentSession = null;
+                            });
+                        }
 
                         final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
                                 (userIdStarted, newSession, halInterfaceVersion) -> {
@@ -136,32 +195,42 @@
 
                         return new FaceStartUserClient(mContext, provider::getHalInstance,
                                 mToken, newUserId, mSensorProperties.sensorId,
-                                BiometricLogger.ofUnknown(mContext), biometricContext,
+                                BiometricLogger.ofUnknown(mContext), mBiometricContext,
                                 resultController, userStartedCallback);
                     }
                 });
-        mLockoutCache = new LockoutCache();
-        mAuthenticatorIds = new HashMap<>();
         mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
+        mLockoutTracker = new LockoutCache();
     }
 
-    Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
-            @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
-            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull BiometricContext biometricContext) {
-        this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher,
-                biometricContext, null);
+    private static FaceSensorPropertiesInternal getFaceSensorPropertiesInternal(SensorProps prop,
+            boolean resetLockoutRequiresChallenge) {
+        final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+        if (prop.commonProps.componentInfo != null) {
+            for (ComponentInfo info : prop.commonProps.componentInfo) {
+                componentInfo.add(new ComponentInfoInternal(info.componentId,
+                        info.hardwareVersion, info.firmwareVersion, info.serialNumber,
+                        info.softwareVersion));
+            }
+        }
+        final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
+                prop.commonProps.sensorId, prop.commonProps.sensorStrength,
+                prop.commonProps.maxEnrollmentsPerUser, componentInfo, prop.sensorType,
+                prop.supportsDetectInteraction, prop.halControlsPreview,
+                resetLockoutRequiresChallenge);
+
+        return internalProp;
     }
 
-    @NonNull Supplier<AidlSession> getLazySession() {
+    @NonNull public Supplier<AidlSession> getLazySession() {
         return mLazySession;
     }
 
-    @NonNull FaceSensorPropertiesInternal getSensorProperties() {
+    @NonNull protected FaceSensorPropertiesInternal getSensorProperties() {
         return mSensorProperties;
     }
 
-    @VisibleForTesting @Nullable AidlSession getSessionForUser(int userId) {
+    @VisibleForTesting @Nullable protected AidlSession getSessionForUser(int userId) {
         if (mCurrentSession != null && mCurrentSession.getUserId() == userId) {
             return mCurrentSession;
         } else {
@@ -174,15 +243,18 @@
                 mProvider, this);
     }
 
-    @NonNull BiometricScheduler getScheduler() {
+    @NonNull public BiometricScheduler getScheduler() {
         return mScheduler;
     }
 
-    @NonNull LockoutCache getLockoutCache() {
-        return mLockoutCache;
+    @NonNull protected LockoutTracker getLockoutTracker(boolean forAuth) {
+        if (forAuth) {
+            return null;
+        }
+        return mLockoutTracker;
     }
 
-    @NonNull Map<Integer, Long> getAuthenticatorIds() {
+    @NonNull protected Map<Integer, Long> getAuthenticatorIds() {
         return mAuthenticatorIds;
     }
 
@@ -253,4 +325,49 @@
         mScheduler.reset();
         mCurrentSession = null;
     }
+
+    protected BiometricContext getBiometricContext() {
+        return mBiometricContext;
+    }
+
+    protected Handler getHandler() {
+        return mHandler;
+    }
+
+    protected Context getContext() {
+        return mContext;
+    }
+
+    /**
+     * Schedules FaceUpdateActiveUserClient for user id.
+     */
+    public void scheduleFaceUpdateActiveUserClient(int userId) {}
+
+    /**
+     * Returns true if the sensor hardware is detected.
+     */
+    public boolean isHardwareDetected(String halInstanceName) {
+        if (mTestHalEnabled) {
+            return true;
+        }
+        return ServiceManager.checkService(IFace.DESCRIPTOR + "/" + halInstanceName) != null;
+    }
+
+    /**
+     * Returns lockout mode of this sensor.
+     */
+    @LockoutTracker.LockoutMode
+    public int getLockoutModeForUser(int userId) {
+        return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
+                Utils.getCurrentStrength(mSensorProperties.sensorId));
+    }
+
+    public void setScheduler(BiometricScheduler scheduler) {
+        mScheduler = scheduler;
+    }
+
+    public void setLazySession(
+            Supplier<AidlSession> lazySession) {
+        mLazySession = lazySession;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
index 8385c3f..0c34d70 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
@@ -27,13 +27,14 @@
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.HalClientMonitor;
+import com.android.server.biometrics.sensors.StartUserClient;
+import com.android.server.biometrics.sensors.face.aidl.AidlSession;
 
 import java.io.File;
 import java.util.Map;
 import java.util.function.Supplier;
 
-public class FaceUpdateActiveUserClient extends HalClientMonitor<IBiometricsFace> {
+public class FaceUpdateActiveUserClient extends StartUserClient<IBiometricsFace, AidlSession> {
     private static final String TAG = "FaceUpdateActiveUserClient";
     private static final String FACE_DATA_DIR = "facedata";
 
@@ -45,8 +46,18 @@
             int sensorId, @NonNull BiometricLogger logger,
             @NonNull BiometricContext biometricContext, boolean hasEnrolledBiometrics,
             @NonNull Map<Integer, Long> authenticatorIds) {
-        super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
-                0 /* cookie */, sensorId, logger, biometricContext);
+        this(context, lazyDaemon, (newUserId, newUser, halInterfaceVersion) -> {},
+                userId, owner, sensorId, logger, biometricContext, hasEnrolledBiometrics,
+                authenticatorIds);
+    }
+
+    FaceUpdateActiveUserClient(@NonNull Context context,
+            @NonNull Supplier<IBiometricsFace> lazyDaemon, UserStartedCallback userStartedCallback,
+            int userId, @NonNull String owner, int sensorId, @NonNull BiometricLogger logger,
+            @NonNull BiometricContext biometricContext, boolean hasEnrolledBiometrics,
+            @NonNull Map<Integer, Long> authenticatorIds) {
+        super(context, lazyDaemon, null /* token */, userId, sensorId, logger, biometricContext,
+                userStartedCallback);
         mHasEnrolledBiometrics = hasEnrolledBiometrics;
         mAuthenticatorIds = authenticatorIds;
     }
@@ -77,6 +88,7 @@
             daemon.setActiveUser(getTargetUserId(), storePath.getAbsolutePath());
             mAuthenticatorIds.put(getTargetUserId(),
                     mHasEnrolledBiometrics ? daemon.getAuthenticatorId().value : 0L);
+            mUserStartedCallback.onUserStarted(getTargetUserId(), null, 0);
             mCallback.onClientFinished(this, true /* success */);
         } catch (RemoteException e) {
             Slog.e(TAG, "Failed to setActiveUser: " + e);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java
index 36a9790..7a574ce 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java
@@ -112,4 +112,8 @@
     void onAuthenticatorIdRetrieved(long authenticatorId) {
         mAidlResponseHandler.onAuthenticatorIdRetrieved(authenticatorId);
     }
+
+    void onUnsupportedClientScheduled() {
+        mAidlResponseHandler.onUnsupportedClientScheduled();
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
new file mode 100644
index 0000000..6355cb5
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
@@ -0,0 +1,248 @@
+/*
+ * 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.biometrics.sensors.face.hidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.hardware.biometrics.face.SensorProps;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.os.Handler;
+import android.os.IHwBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.StartUserClient;
+import com.android.server.biometrics.sensors.face.FaceUtils;
+import com.android.server.biometrics.sensors.face.LockoutHalImpl;
+import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.face.aidl.AidlSession;
+import com.android.server.biometrics.sensors.face.aidl.FaceProvider;
+import com.android.server.biometrics.sensors.face.aidl.Sensor;
+
+/**
+ * Convert HIDL sensor configurations to an AIDL Sensor.
+ */
+public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRecipient{
+
+    private static final String TAG = "HidlToAidlSensorAdapter";
+
+    private IBiometricsFace mDaemon;
+    private AidlSession mSession;
+    private int mCurrentUserId = UserHandle.USER_NULL;
+    private final Runnable mInternalCleanupAndGetFeatureRunnable;
+    private final FaceProvider mFaceProvider;
+    private final LockoutResetDispatcher mLockoutResetDispatcher;
+    private final AuthSessionCoordinator mAuthSessionCoordinator;
+    private final AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+    private final StartUserClient.UserStartedCallback<AidlSession> mUserStartedCallback =
+            (newUserId, newUser, halInterfaceVersion) -> {
+                if (newUserId != mCurrentUserId) {
+                    handleUserChanged(newUserId);
+                }
+            };
+    private LockoutHalImpl mLockoutTracker;
+
+    public HidlToAidlSensorAdapter(@NonNull String tag,
+            @NonNull FaceProvider provider,
+            @NonNull Context context,
+            @NonNull Handler handler,
+            @NonNull SensorProps prop,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull BiometricContext biometricContext,
+            boolean resetLockoutRequiresChallenge,
+            @NonNull Runnable internalCleanupAndGetFeatureRunnable) {
+        this(tag, provider, context, handler, prop, lockoutResetDispatcher, biometricContext,
+                resetLockoutRequiresChallenge, internalCleanupAndGetFeatureRunnable,
+                new AuthSessionCoordinator(), null /* daemon */,
+                null /* onEnrollSuccessCallback */);
+    }
+
+    @VisibleForTesting
+    HidlToAidlSensorAdapter(@NonNull String tag,
+            @NonNull FaceProvider provider,
+            @NonNull Context context,
+            @NonNull Handler handler,
+            @NonNull SensorProps prop,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull BiometricContext biometricContext,
+            boolean resetLockoutRequiresChallenge,
+            @NonNull Runnable internalCleanupAndGetFeatureRunnable,
+            @NonNull AuthSessionCoordinator authSessionCoordinator,
+            @Nullable IBiometricsFace daemon,
+            @Nullable AidlResponseHandler.AidlResponseHandlerCallback aidlResponseHandlerCallback) {
+        super(tag, provider, context, handler, prop, lockoutResetDispatcher, biometricContext,
+                resetLockoutRequiresChallenge);
+        mInternalCleanupAndGetFeatureRunnable = internalCleanupAndGetFeatureRunnable;
+        mFaceProvider = provider;
+        mLockoutResetDispatcher = lockoutResetDispatcher;
+        mAuthSessionCoordinator = authSessionCoordinator;
+        mDaemon = daemon;
+        mAidlResponseHandlerCallback = aidlResponseHandlerCallback == null
+                ? new AidlResponseHandler.AidlResponseHandlerCallback() {
+                    @Override
+                    public void onEnrollSuccess() {
+                        scheduleFaceUpdateActiveUserClient(mCurrentUserId);
+                    }
+
+                    @Override
+                    public void onHardwareUnavailable() {
+                        mDaemon = null;
+                        mCurrentUserId = UserHandle.USER_NULL;
+                    }
+                } : aidlResponseHandlerCallback;
+    }
+
+    @Override
+    public void scheduleFaceUpdateActiveUserClient(int userId) {
+        getScheduler().scheduleClientMonitor(getFaceUpdateActiveUserClient(userId));
+    }
+
+    @Override
+    public void serviceDied(long cookie) {
+        Slog.d(TAG, "HAL died.");
+        mDaemon = null;
+    }
+
+    @Override
+    public boolean isHardwareDetected(String halInstanceName) {
+        return getIBiometricsFace() != null;
+    }
+
+    @Override
+    @LockoutTracker.LockoutMode
+    public int getLockoutModeForUser(int userId) {
+        return mLockoutTracker.getLockoutModeForUser(userId);
+    }
+
+    @Override
+    public void init(LockoutResetDispatcher lockoutResetDispatcher,
+            FaceProvider provider) {
+        setScheduler(new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_FACE,
+                null /* gestureAvailabilityTracker */));
+        setLazySession(this::getSession);
+        mLockoutTracker = new LockoutHalImpl();
+    }
+
+    @Override
+    @VisibleForTesting @Nullable protected AidlSession getSessionForUser(int userId) {
+        if (mSession != null && mSession.getUserId() == userId) {
+            return mSession;
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    protected LockoutTracker getLockoutTracker(boolean forAuth) {
+        return mLockoutTracker;
+    }
+
+    @NonNull AidlSession getSession() {
+        if (mDaemon != null && mSession != null) {
+            return mSession;
+        } else {
+            return mSession = new AidlSession(getContext(), this::getIBiometricsFace,
+                    mCurrentUserId, getAidlResponseHandler());
+        }
+    }
+
+    private AidlResponseHandler getAidlResponseHandler() {
+        return new AidlResponseHandler(getContext(), getScheduler(), getSensorProperties().sensorId,
+                mCurrentUserId, mLockoutTracker, mLockoutResetDispatcher,
+                mAuthSessionCoordinator, () -> {}, mAidlResponseHandlerCallback);
+    }
+
+    private IBiometricsFace getIBiometricsFace() {
+        if (mFaceProvider.getTestHalEnabled()) {
+            final TestHal testHal = new TestHal(getContext(), getSensorProperties().sensorId);
+            testHal.setCallback(new HidlToAidlCallbackConverter(getAidlResponseHandler()));
+            return testHal;
+        }
+
+        if (mDaemon != null) {
+            return mDaemon;
+        }
+
+        Slog.d(TAG, "Daemon was null, reconnecting, current operation: "
+                + getScheduler().getCurrentClient());
+
+        try {
+            mDaemon = IBiometricsFace.getService();
+        } catch (java.util.NoSuchElementException e) {
+            // Service doesn't exist or cannot be opened.
+            Slog.w(TAG, "NoSuchElementException", e);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to get face HAL", e);
+        }
+
+        if (mDaemon == null) {
+            Slog.w(TAG, "Face HAL not available");
+            return null;
+        }
+
+        mDaemon.asBinder().linkToDeath(this, 0 /* flags */);
+
+        scheduleLoadAuthenticatorIds();
+        mInternalCleanupAndGetFeatureRunnable.run();
+        return mDaemon;
+    }
+
+    @VisibleForTesting void handleUserChanged(int newUserId) {
+        Slog.d(TAG, "User changed. Current user is " + newUserId);
+        mSession = null;
+        mCurrentUserId = newUserId;
+    }
+
+    private void scheduleLoadAuthenticatorIds() {
+        // Note that this can be performed on the scheduler (as opposed to being done immediately
+        // when the HAL is (re)loaded, since
+        // 1) If this is truly the first time it's being performed (e.g. system has just started),
+        //    this will be run very early and way before any applications need to generate keys.
+        // 2) If this is being performed to refresh the authenticatorIds (e.g. HAL crashed and has
+        //    just been reloaded), the framework already has a cache of the authenticatorIds. This
+        //    is safe because authenticatorIds only change when A) new template has been enrolled,
+        //    or B) all templates are removed.
+        getHandler().post(() -> {
+            for (UserInfo user : UserManager.get(getContext()).getAliveUsers()) {
+                final int targetUserId = user.id;
+                if (!getAuthenticatorIds().containsKey(targetUserId)) {
+                    scheduleFaceUpdateActiveUserClient(targetUserId);
+                }
+            }
+        });
+    }
+
+    private FaceUpdateActiveUserClient getFaceUpdateActiveUserClient(int userId) {
+        return new FaceUpdateActiveUserClient(getContext(), this::getIBiometricsFace,
+                mUserStartedCallback, userId, TAG, getSensorProperties().sensorId,
+                BiometricLogger.ofUnknown(getContext()), getBiometricContext(),
+                !FaceUtils.getInstance(getSensorProperties().sensorId).getBiometricsForUser(
+                        getContext(), userId).isEmpty(),
+                getAuthenticatorIds());
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java
similarity index 88%
rename from services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java
rename to services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java
index 489b213..5daf2d4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java
@@ -47,34 +47,37 @@
 import java.util.function.Supplier;
 
 /**
- * Adapter to convert AIDL-specific interface {@link ISession} methods to HIDL implementation.
+ * Adapter to convert HIDL methods into AIDL interface {@link ISession}.
  */
-public class AidlToHidlAdapter implements ISession {
+public class HidlToAidlSessionAdapter implements ISession {
 
-    private final String TAG = "AidlToHidlAdapter";
+    private static final String TAG = "HidlToAidlSessionAdapter";
+
     private static final int CHALLENGE_TIMEOUT_SEC = 600;
     @DurationMillisLong
     private static final int GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS = 60 * 1000;
     @DurationMillisLong
     private static final int GENERATE_CHALLENGE_COUNTER_TTL_MILLIS = CHALLENGE_TIMEOUT_SEC * 1000;
     private static final int INVALID_VALUE = -1;
+    @VisibleForTesting static final int ENROLL_TIMEOUT_SEC = 75;
+
     private final Clock mClock;
     private final List<Long> mGeneratedChallengeCount = new ArrayList<>();
-    @VisibleForTesting static final int ENROLL_TIMEOUT_SEC = 75;
+    private final int mUserId;
+    private final Context mContext;
+
     private long mGenerateChallengeCreatedAt = INVALID_VALUE;
     private long mGenerateChallengeResult = INVALID_VALUE;
     @NonNull private Supplier<IBiometricsFace> mSession;
-    private final int mUserId;
     private HidlToAidlCallbackConverter mHidlToAidlCallbackConverter;
-    private final Context mContext;
     private int mFeature = INVALID_VALUE;
 
-    public AidlToHidlAdapter(Context context, Supplier<IBiometricsFace> session,
+    public HidlToAidlSessionAdapter(Context context, Supplier<IBiometricsFace> session,
             int userId, AidlResponseHandler aidlResponseHandler) {
         this(context, session, userId, aidlResponseHandler, Clock.systemUTC());
     }
 
-    AidlToHidlAdapter(Context context, Supplier<IBiometricsFace> session, int userId,
+    HidlToAidlSessionAdapter(Context context, Supplier<IBiometricsFace> session, int userId,
             AidlResponseHandler aidlResponseHandler, Clock clock) {
         mSession = session;
         mUserId = userId;
@@ -83,42 +86,11 @@
         setCallback(aidlResponseHandler);
     }
 
-    private void setCallback(AidlResponseHandler aidlResponseHandler) {
-        mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler);
-        try {
-            mSession.get().setCallback(mHidlToAidlCallbackConverter);
-        } catch (RemoteException e) {
-            Slog.d(TAG, "Failed to set callback");
-        }
-    }
-
     @Override
     public IBinder asBinder() {
         return null;
     }
 
-    private boolean isGeneratedChallengeCacheValid() {
-        return mGenerateChallengeCreatedAt != INVALID_VALUE
-                && mGenerateChallengeResult != INVALID_VALUE
-                && mClock.millis() - mGenerateChallengeCreatedAt
-                < GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS;
-    }
-
-    private void incrementChallengeCount() {
-        mGeneratedChallengeCount.add(0, mClock.millis());
-    }
-
-    private int decrementChallengeCount() {
-        final long now = mClock.millis();
-        // ignore values that are old in case generate/revoke calls are not matched
-        // this doesn't ensure revoke if calls are mismatched but it keeps the list from growing
-        mGeneratedChallengeCount.removeIf(x -> now - x > GENERATE_CHALLENGE_COUNTER_TTL_MILLIS);
-        if (!mGeneratedChallengeCount.isEmpty()) {
-            mGeneratedChallengeCount.remove(0);
-        }
-        return mGeneratedChallengeCount.size();
-    }
-
     @Override
     public void generateChallenge() throws RemoteException {
         incrementChallengeCount();
@@ -150,7 +122,7 @@
 
     @Override
     public EnrollmentStageConfig[] getEnrollmentConfig(byte enrollmentType) throws RemoteException {
-        //unsupported in HIDL
+        Slog.e(TAG, "getEnrollmentConfig unsupported in HIDL");
         return null;
     }
 
@@ -244,19 +216,6 @@
         }
     }
 
-    private int getFaceId() {
-        FaceManager faceManager = mContext.getSystemService(FaceManager.class);
-        List<Face> faces = faceManager.getEnrolledFaces(mUserId);
-        if (faces.isEmpty()) {
-            Slog.d(TAG, "No faces to get feature from.");
-            mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId,
-                    BiometricFaceConstants.FACE_ERROR_NOT_ENROLLED, 0 /* vendorCode */);
-            return INVALID_VALUE;
-        }
-
-        return faces.get(0).getBiometricId();
-    }
-
     @Override
     public void getAuthenticatorId() throws RemoteException {
         long authenticatorId = mSession.get().getAuthenticatorId().value;
@@ -265,7 +224,8 @@
 
     @Override
     public void invalidateAuthenticatorId() throws RemoteException {
-        //unsupported in HIDL
+        Slog.e(TAG, "invalidateAuthenticatorId unsupported in HIDL");
+        mHidlToAidlCallbackConverter.onUnsupportedClientScheduled();
     }
 
     @Override
@@ -279,47 +239,105 @@
 
     @Override
     public void close() throws RemoteException {
-        //Unsupported in HIDL
+        Slog.e(TAG, "close unsupported in HIDL");
     }
 
     @Override
     public ICancellationSignal authenticateWithContext(long operationId, OperationContext context)
             throws RemoteException {
-        //Unsupported in HIDL
-        return null;
+        Slog.e(TAG, "authenticateWithContext unsupported in HIDL");
+        return authenticate(operationId);
     }
 
     @Override
     public ICancellationSignal enrollWithContext(HardwareAuthToken hat, byte type, byte[] features,
             NativeHandle previewSurface, OperationContext context) throws RemoteException {
-        //Unsupported in HIDL
-        return null;
+        Slog.e(TAG, "enrollWithContext unsupported in HIDL");
+        return enroll(hat, type, features, previewSurface);
     }
 
     @Override
     public ICancellationSignal detectInteractionWithContext(OperationContext context)
             throws RemoteException {
-        //Unsupported in HIDL
-        return null;
+        Slog.e(TAG, "detectInteractionWithContext unsupported in HIDL");
+        return detectInteraction();
     }
 
     @Override
     public void onContextChanged(OperationContext context) throws RemoteException {
-        //Unsupported in HIDL
+        Slog.e(TAG, "onContextChanged unsupported in HIDL");
     }
 
     @Override
     public int getInterfaceVersion() throws RemoteException {
-        //Unsupported in HIDL
+        Slog.e(TAG, "getInterfaceVersion unsupported in HIDL");
         return 0;
     }
 
     @Override
     public String getInterfaceHash() throws RemoteException {
+        Slog.e(TAG, "getInterfaceHash unsupported in HIDL");
+        return null;
+    }
+
+    @Override
+    public ICancellationSignal enrollWithOptions(FaceEnrollOptions options) {
         //Unsupported in HIDL
         return null;
     }
 
+    private boolean isGeneratedChallengeCacheValid() {
+        return mGenerateChallengeCreatedAt != INVALID_VALUE
+                && mGenerateChallengeResult != INVALID_VALUE
+                && mClock.millis() - mGenerateChallengeCreatedAt
+                < GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS;
+    }
+
+    private void incrementChallengeCount() {
+        mGeneratedChallengeCount.add(0, mClock.millis());
+    }
+
+    private int decrementChallengeCount() {
+        final long now = mClock.millis();
+        // ignore values that are old in case generate/revoke calls are not matched
+        // this doesn't ensure revoke if calls are mismatched but it keeps the list from growing
+        mGeneratedChallengeCount.removeIf(x -> now - x > GENERATE_CHALLENGE_COUNTER_TTL_MILLIS);
+        if (!mGeneratedChallengeCount.isEmpty()) {
+            mGeneratedChallengeCount.remove(0);
+        }
+        return mGeneratedChallengeCount.size();
+    }
+
+    private void setCallback(AidlResponseHandler aidlResponseHandler) {
+        mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler);
+        try {
+            if (mSession.get() != null) {
+                long halId = mSession.get().setCallback(mHidlToAidlCallbackConverter).value;
+                Slog.d(TAG, "Face HAL ready, HAL ID: " + halId);
+                if (halId == 0) {
+                    Slog.d(TAG, "Unable to set HIDL callback.");
+                }
+            } else {
+                Slog.e(TAG, "Unable to set HIDL callback. HIDL daemon is null.");
+            }
+        } catch (RemoteException e) {
+            Slog.d(TAG, "Failed to set callback");
+        }
+    }
+
+    private int getFaceId() {
+        FaceManager faceManager = mContext.getSystemService(FaceManager.class);
+        List<Face> faces = faceManager.getEnrolledFaces(mUserId);
+        if (faces.isEmpty()) {
+            Slog.d(TAG, "No faces to get feature from.");
+            mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId,
+                    BiometricFaceConstants.FACE_ERROR_NOT_ENROLLED, 0 /* vendorCode */);
+            return INVALID_VALUE;
+        }
+
+        return faces.get(0).getBiometricId();
+    }
+
     /**
      * Cancellation in HIDL occurs for the entire session, instead of a specific client.
      */
@@ -345,10 +363,4 @@
             return null;
         }
     }
-
-    @Override
-    public ICancellationSignal enrollWithOptions(FaceEnrollOptions options) {
-        //Unsupported in HIDL
-        return null;
-    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 83b306b..e01d672 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -45,9 +45,11 @@
 import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.fingerprint.IFingerprint;
 import android.hardware.biometrics.fingerprint.PointerContext;
+import android.hardware.biometrics.fingerprint.SensorProps;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorConfigurations;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.FingerprintServiceReceiver;
 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
@@ -80,6 +82,7 @@
 import com.android.internal.util.DumpUtils;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.SystemService;
+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;
@@ -127,6 +130,8 @@
     @NonNull
     private final Function<String, FingerprintProvider> mFingerprintProvider;
     @NonNull
+    private final FingerprintProviderFunction mFingerprintProviderFunction;
+    @NonNull
     private final BiometricStateCallback<ServiceProvider, FingerprintSensorPropertiesInternal>
             mBiometricStateCallback;
     @NonNull
@@ -136,6 +141,11 @@
     @NonNull
     private final FingerprintServiceRegistry mRegistry;
 
+    interface FingerprintProviderFunction {
+        FingerprintProvider getFingerprintProvider(Pair<String, SensorProps[]> filteredSensorProp,
+                boolean resetLockoutRequiresHardwareAuthToken);
+    }
+
     /** Receives the incoming binder calls from FingerprintManager. */
     @VisibleForTesting
     final IFingerprintService.Stub mServiceWrapper = new IFingerprintService.Stub() {
@@ -874,6 +884,18 @@
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
+        public void registerAuthenticatorsLegacy(
+                @NonNull FingerprintSensorConfigurations fingerprintSensorConfigurations) {
+            super.registerAuthenticatorsLegacy_enforcePermission();
+            if (!fingerprintSensorConfigurations.hasSensorConfigurations()) {
+                Slog.d(TAG, "No fingerprint sensors available.");
+                return;
+            }
+            mRegistry.registerAll(() -> getProviders(fingerprintSensorConfigurations));
+        }
+
+        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+        @Override // Binder call
         public void registerAuthenticators(
                 @NonNull List<FingerprintSensorPropertiesInternal> hidlSensors) {
             super.registerAuthenticators_enforcePermission();
@@ -1021,7 +1043,8 @@
                 () -> IBiometricService.Stub.asInterface(
                         ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
                 () -> ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR),
-                null /* fingerprintProvider */);
+                null /* fingerprintProvider */,
+                null /* fingerprintProviderFunction */);
     }
 
     @VisibleForTesting
@@ -1029,7 +1052,8 @@
             BiometricContext biometricContext,
             Supplier<IBiometricService> biometricServiceSupplier,
             Supplier<String[]> aidlInstanceNameSupplier,
-            Function<String, FingerprintProvider> fingerprintProvider) {
+            Function<String, FingerprintProvider> fingerprintProvider,
+            FingerprintProviderFunction fingerprintProviderFunction) {
         super(context);
         mBiometricContext = biometricContext;
         mAidlInstanceNameSupplier = aidlInstanceNameSupplier;
@@ -1049,7 +1073,8 @@
                             return new FingerprintProvider(getContext(),
                                     mBiometricStateCallback, mAuthenticationStateListeners,
                                     fp.getSensorProps(), name, mLockoutResetDispatcher,
-                                    mGestureAvailabilityDispatcher, mBiometricContext);
+                                    mGestureAvailabilityDispatcher, mBiometricContext,
+                                    true /* resetLockoutRequiresHardwareAuthToken */);
                         } catch (RemoteException e) {
                             Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
                         }
@@ -1059,6 +1084,22 @@
 
                     return null;
                 };
+        if (Flags.deHidl()) {
+            mFingerprintProviderFunction = fingerprintProviderFunction == null
+                    ? (filteredSensorProps, resetLockoutRequiresHardwareAuthToken) ->
+                            new FingerprintProvider(
+                                    getContext(), mBiometricStateCallback,
+                                    mAuthenticationStateListeners,
+                                    filteredSensorProps.second,
+                                    filteredSensorProps.first, mLockoutResetDispatcher,
+                                    mGestureAvailabilityDispatcher,
+                                    mBiometricContext,
+                                    resetLockoutRequiresHardwareAuthToken)
+                    : fingerprintProviderFunction;
+        } else {
+            mFingerprintProviderFunction =
+                    (filteredSensorProps, resetLockoutRequiresHardwareAuthToken) -> null;
+        }
         mHandler = new Handler(Looper.getMainLooper());
         mRegistry = new FingerprintServiceRegistry(mServiceWrapper, biometricServiceSupplier);
         mRegistry.addAllRegisteredCallback(new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
@@ -1070,6 +1111,44 @@
         });
     }
 
+    @NonNull
+    private List<ServiceProvider> getProviders(@NonNull FingerprintSensorConfigurations
+            fingerprintSensorConfigurations) {
+        final List<ServiceProvider> providers = new ArrayList<>();
+        final Pair<String, SensorProps[]> filteredSensorProps = filterAvailableHalInstances(
+                fingerprintSensorConfigurations);
+        providers.add(mFingerprintProviderFunction.getFingerprintProvider(filteredSensorProps,
+                fingerprintSensorConfigurations.getResetLockoutRequiresHardwareAuthToken()));
+
+        return providers;
+    }
+
+    @NonNull
+    private Pair<String, SensorProps[]> filterAvailableHalInstances(
+            FingerprintSensorConfigurations fingerprintSensorConfigurations) {
+        Pair<String, SensorProps[]> finalSensorPair =
+                fingerprintSensorConfigurations.getSensorPair();
+        if (fingerprintSensorConfigurations.isSingleSensorConfigurationPresent()) {
+            return finalSensorPair;
+        }
+
+        final Pair<String, SensorProps[]> virtualSensorPropsPair = fingerprintSensorConfigurations
+                .getSensorPairForInstance("virtual");
+        if (Utils.isVirtualEnabled(getContext())) {
+            if (virtualSensorPropsPair != null) {
+                return virtualSensorPropsPair;
+            } else {
+                Slog.e(TAG, "Could not find virtual interface while it is enabled");
+                return finalSensorPair;
+            }
+        } else {
+            if (virtualSensorPropsPair != null) {
+                return fingerprintSensorConfigurations.getSensorPairNotForInstance("virtual");
+            }
+        }
+        return finalSensorPair;
+    }
+
     private Pair<List<FingerprintSensorPropertiesInternal>, List<String>>
                 filterAvailableHalInstances(
             @NonNull List<FingerprintSensorPropertiesInternal> hidlInstances,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
index 4a01943..bd21cf4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
@@ -25,6 +25,7 @@
 import android.hardware.keymaster.HardwareAuthToken;
 import android.util.Slog;
 
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.sensors.AcquisitionClient;
@@ -34,9 +35,9 @@
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.EnumerateConsumer;
 import com.android.server.biometrics.sensors.ErrorConsumer;
-import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutConsumer;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.RemovalConsumer;
 import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
 
@@ -59,6 +60,21 @@
         void onHardwareUnavailable();
     }
 
+    /**
+     * Interface to send results to the AidlResponseHandler's owner.
+     */
+    public interface AidlResponseHandlerCallback {
+        /**
+         * Invoked when enrollment is successful.
+         */
+        void onEnrollSuccess();
+
+        /**
+         * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
+         */
+        void onHardwareUnavailable();
+    }
+
     private static final String TAG = "AidlResponseHandler";
 
     @NonNull
@@ -68,28 +84,49 @@
     private final int mSensorId;
     private final int mUserId;
     @NonNull
-    private final LockoutCache mLockoutCache;
+    private final LockoutTracker mLockoutTracker;
     @NonNull
     private final LockoutResetDispatcher mLockoutResetDispatcher;
     @NonNull
     private final AuthSessionCoordinator mAuthSessionCoordinator;
     @NonNull
     private final HardwareUnavailableCallback mHardwareUnavailableCallback;
+    @NonNull
+    private final AidlResponseHandlerCallback mAidlResponseHandlerCallback;
 
     public AidlResponseHandler(@NonNull Context context,
             @NonNull BiometricScheduler scheduler, int sensorId, int userId,
-            @NonNull LockoutCache lockoutTracker,
+            @NonNull LockoutTracker lockoutTracker,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull AuthSessionCoordinator authSessionCoordinator,
             @NonNull HardwareUnavailableCallback hardwareUnavailableCallback) {
+        this(context, scheduler, sensorId, userId, lockoutTracker, lockoutResetDispatcher,
+                authSessionCoordinator, hardwareUnavailableCallback,
+                new AidlResponseHandlerCallback() {
+                    @Override
+                    public void onEnrollSuccess() {}
+
+                    @Override
+                    public void onHardwareUnavailable() {}
+                });
+    }
+
+    public AidlResponseHandler(@NonNull Context context,
+            @NonNull BiometricScheduler scheduler, int sensorId, int userId,
+            @NonNull LockoutTracker lockoutTracker,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull AuthSessionCoordinator authSessionCoordinator,
+            @NonNull HardwareUnavailableCallback hardwareUnavailableCallback,
+            @NonNull AidlResponseHandlerCallback aidlResponseHandlerCallback) {
         mContext = context;
         mScheduler = scheduler;
         mSensorId = sensorId;
         mUserId = userId;
-        mLockoutCache = lockoutTracker;
+        mLockoutTracker = lockoutTracker;
         mLockoutResetDispatcher = lockoutResetDispatcher;
         mAuthSessionCoordinator = authSessionCoordinator;
         mHardwareUnavailableCallback = hardwareUnavailableCallback;
+        mAidlResponseHandlerCallback = aidlResponseHandlerCallback;
     }
 
     @Override
@@ -105,27 +142,26 @@
     @Override
     public void onChallengeGenerated(long challenge) {
         handleResponse(FingerprintGenerateChallengeClient.class, (c) -> c.onChallengeGenerated(
-                mSensorId, mUserId, challenge), null);
+                mSensorId, mUserId, challenge));
     }
 
     @Override
     public void onChallengeRevoked(long challenge) {
         handleResponse(FingerprintRevokeChallengeClient.class, (c) -> c.onChallengeRevoked(
-                challenge), null);
+                challenge));
     }
 
     /**
      * Handles acquired messages sent by the HAL (specifically for HIDL HAL).
      */
     public void onAcquired(int acquiredInfo, int vendorCode) {
-        handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode),
-                null);
+        handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode));
     }
 
     @Override
     public void onAcquired(byte info, int vendorCode) {
         handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(
-                AidlConversionUtils.toFrameworkAcquiredInfo(info), vendorCode), null);
+                AidlConversionUtils.toFrameworkAcquiredInfo(info), vendorCode));
     }
 
     /**
@@ -135,9 +171,13 @@
         handleResponse(ErrorConsumer.class, (c) -> {
             c.onError(error, vendorCode);
             if (error == Error.HW_UNAVAILABLE) {
-                mHardwareUnavailableCallback.onHardwareUnavailable();
+                if (Flags.deHidl()) {
+                    mAidlResponseHandlerCallback.onHardwareUnavailable();
+                } else {
+                    mHardwareUnavailableCallback.onHardwareUnavailable();
+                }
             }
-        }, null);
+        });
     }
 
     @Override
@@ -158,8 +198,12 @@
                 .getUniqueName(mContext, currentUserId);
         final Fingerprint fingerprint = new Fingerprint(name, currentUserId,
                 enrollmentId, mSensorId);
-        handleResponse(FingerprintEnrollClient.class, (c) -> c.onEnrollResult(fingerprint,
-                remaining), null);
+        handleResponse(FingerprintEnrollClient.class, (c) -> {
+            c.onEnrollResult(fingerprint, remaining);
+            if (remaining == 0) {
+                mAidlResponseHandlerCallback.onEnrollSuccess();
+            }
+        });
     }
 
     @Override
@@ -184,13 +228,12 @@
 
     @Override
     public void onLockoutTimed(long durationMillis) {
-        handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis),
-                null);
+        handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis));
     }
 
     @Override
     public void onLockoutPermanent() {
-        handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent, null);
+        handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent);
     }
 
     @Override
@@ -198,7 +241,7 @@
         handleResponse(FingerprintResetLockoutClient.class,
                 FingerprintResetLockoutClient::onLockoutCleared,
                 (c) -> FingerprintResetLockoutClient.resetLocalLockoutStateToNone(
-                        mSensorId, mUserId, mLockoutCache, mLockoutResetDispatcher,
+                        mSensorId, mUserId, mLockoutTracker, mLockoutResetDispatcher,
                         mAuthSessionCoordinator, Utils.getCurrentStrength(mSensorId),
                         -1 /* requestId */));
     }
@@ -206,49 +249,74 @@
     @Override
     public void onInteractionDetected() {
         handleResponse(FingerprintDetectClient.class,
-                FingerprintDetectClient::onInteractionDetected, null);
+                FingerprintDetectClient::onInteractionDetected);
     }
 
     @Override
     public void onEnrollmentsEnumerated(int[] enrollmentIds) {
         if (enrollmentIds.length > 0) {
             for (int i = 0; i < enrollmentIds.length; i++) {
-                final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId);
-                int finalI = i;
-                handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(fp,
-                        enrollmentIds.length - finalI - 1), null);
+                onEnrollmentEnumerated(enrollmentIds[i],
+                        enrollmentIds.length - i - 1 /* remaining */);
             }
         } else {
-            handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(null,
-                    0), null);
+            handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(
+                    null /* identifier */,
+                    0 /* remaining */));
         }
     }
 
+    /**
+     * Handle enumerated fingerprint.
+     */
+    public void onEnrollmentEnumerated(int enrollmentId, int remaining) {
+        final Fingerprint fp = new Fingerprint("", enrollmentId, mSensorId);
+        handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(fp, remaining));
+    }
+
+    /**
+     * Handle removal of fingerprint.
+     */
+    public void onEnrollmentRemoved(int enrollmentId, int remaining) {
+        final Fingerprint fp = new Fingerprint("", enrollmentId, mSensorId);
+        handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(fp, remaining));
+    }
+
     @Override
     public void onEnrollmentsRemoved(int[] enrollmentIds) {
         if (enrollmentIds.length > 0) {
             for (int i  = 0; i < enrollmentIds.length; i++) {
-                final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId);
-                int finalI = i;
-                handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(fp,
-                        enrollmentIds.length - finalI - 1), null);
+                onEnrollmentRemoved(enrollmentIds[i], enrollmentIds.length - i - 1 /* remaining */);
             }
         } else {
-            handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(null, 0),
-                    null);
+            handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(null /* identifier */,
+                    0 /* remaining */));
         }
     }
 
     @Override
     public void onAuthenticatorIdRetrieved(long authenticatorId) {
         handleResponse(FingerprintGetAuthenticatorIdClient.class,
-                (c) -> c.onAuthenticatorIdRetrieved(authenticatorId), null);
+                (c) -> c.onAuthenticatorIdRetrieved(authenticatorId));
     }
 
     @Override
     public void onAuthenticatorIdInvalidated(long newAuthenticatorId) {
         handleResponse(FingerprintInvalidationClient.class, (c) -> c.onAuthenticatorIdInvalidated(
-                newAuthenticatorId), null);
+                newAuthenticatorId));
+    }
+
+    /**
+     * Handle clients which are not supported in HIDL HAL.
+     */
+    public <T extends BaseClientMonitor> void onUnsupportedClientScheduled(Class<T> className) {
+        Slog.e(TAG, className + " is not supported in the HAL.");
+        handleResponse(className, (c) -> c.cancel());
+    }
+
+    private <T> void handleResponse(@NonNull Class<T> className,
+            @NonNull Consumer<T> action) {
+        handleResponse(className, action, null /* alternateAction */);
     }
 
     private <T> void handleResponse(@NonNull Class<T> className,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
index 299a310..8ff105b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
@@ -20,7 +20,7 @@
 import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 
-import com.android.server.biometrics.sensors.fingerprint.hidl.AidlToHidlAdapter;
+import com.android.server.biometrics.sensors.fingerprint.hidl.HidlToAidlSessionAdapter;
 
 import java.util.function.Supplier;
 
@@ -45,7 +45,7 @@
 
     public AidlSession(@NonNull Supplier<IBiometricsFingerprint> session,
             int userId, AidlResponseHandler aidlResponseHandler) {
-        mSession = new AidlToHidlAdapter(session, userId, aidlResponseHandler);
+        mSession = new HidlToAidlSessionAdapter(session, userId, aidlResponseHandler);
         mHalInterfaceVersion = 0;
         mUserId = userId;
         mAidlResponseHandler = aidlResponseHandler;
@@ -62,7 +62,7 @@
     }
 
     /** The HAL callback, which should only be used in tests {@See BiometricTestSessionImpl}. */
-    AidlResponseHandler getHalSessionCallback() {
+    public AidlResponseHandler getHalSessionCallback() {
         return mAidlResponseHandler;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
index ea1a622..0353969 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
@@ -30,7 +30,7 @@
 import java.util.Map;
 import java.util.function.Supplier;
 
-class FingerprintGetAuthenticatorIdClient extends HalClientMonitor<AidlSession> {
+public class FingerprintGetAuthenticatorIdClient extends HalClientMonitor<AidlSession> {
 
     private static final String TAG = "FingerprintGetAuthenticatorIdClient";
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 032ab87..88a11d9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -59,6 +59,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
 import com.android.server.biometrics.AuthenticationStatsCollector;
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
@@ -73,6 +74,7 @@
 import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
 import com.android.server.biometrics.sensors.InvalidationRequesterClient;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.PerformanceTracker;
 import com.android.server.biometrics.sensors.SensorList;
 import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
@@ -80,6 +82,7 @@
 import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
 import com.android.server.biometrics.sensors.fingerprint.ServiceProvider;
 import com.android.server.biometrics.sensors.fingerprint.Udfps;
+import com.android.server.biometrics.sensors.fingerprint.hidl.HidlToAidlSensorAdapter;
 
 import org.json.JSONArray;
 import org.json.JSONException;
@@ -165,10 +168,12 @@
             @NonNull SensorProps[] props, @NonNull String halInstanceName,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
-            @NonNull BiometricContext biometricContext) {
+            @NonNull BiometricContext biometricContext,
+            boolean resetLockoutRequiresHardwareAuthToken) {
         this(context, biometricStateCallback, authenticationStateListeners, props, halInstanceName,
                 lockoutResetDispatcher, gestureAvailabilityDispatcher, biometricContext,
-                null /* daemon */);
+                null /* daemon */, resetLockoutRequiresHardwareAuthToken,
+                false /* testHalEnabled */);
     }
 
     @VisibleForTesting FingerprintProvider(@NonNull Context context,
@@ -178,7 +183,9 @@
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
             @NonNull BiometricContext biometricContext,
-            IFingerprint daemon) {
+            @Nullable IFingerprint daemon,
+            boolean resetLockoutRequiresHardwareAuthToken,
+            boolean testHalEnabled) {
         mContext = context;
         mBiometricStateCallback = biometricStateCallback;
         mAuthenticationStateListeners = authenticationStateListeners;
@@ -191,62 +198,136 @@
         mBiometricContext = biometricContext;
         mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
         mDaemon = daemon;
+        mTestHalEnabled = testHalEnabled;
 
-        AuthenticationStatsBroadcastReceiver mBroadcastReceiver =
-                new AuthenticationStatsBroadcastReceiver(
-                        mContext,
-                        BiometricsProtoEnums.MODALITY_FINGERPRINT,
-                        (AuthenticationStatsCollector collector) -> {
-                            Slog.d(getTag(), "Initializing AuthenticationStatsCollector");
-                            mAuthenticationStatsCollector = collector;
-                        });
+        initAuthenticationBroadcastReceiver();
+        initSensors(resetLockoutRequiresHardwareAuthToken, props, gestureAvailabilityDispatcher);
+    }
 
-        final List<SensorLocationInternal> workaroundLocations = getWorkaroundSensorProps(context);
+    private void initAuthenticationBroadcastReceiver() {
+        new AuthenticationStatsBroadcastReceiver(
+                mContext,
+                BiometricsProtoEnums.MODALITY_FINGERPRINT,
+                (AuthenticationStatsCollector collector) -> {
+                    Slog.d(getTag(), "Initializing AuthenticationStatsCollector");
+                    mAuthenticationStatsCollector = collector;
+                });
+    }
 
-        for (SensorProps prop : props) {
-            final int sensorId = prop.commonProps.sensorId;
-
-            final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
-            if (prop.commonProps.componentInfo != null) {
-                for (ComponentInfo info : prop.commonProps.componentInfo) {
-                    componentInfo.add(new ComponentInfoInternal(info.componentId,
-                            info.hardwareVersion, info.firmwareVersion, info.serialNumber,
-                            info.softwareVersion));
+    private void initSensors(boolean resetLockoutRequiresHardwareAuthToken, SensorProps[] props,
+            GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+        if (Flags.deHidl()) {
+            if (!resetLockoutRequiresHardwareAuthToken) {
+                Slog.d(getTag(), "Adding HIDL configs");
+                for (SensorProps sensorConfig: props) {
+                    addHidlSensors(sensorConfig, gestureAvailabilityDispatcher,
+                            resetLockoutRequiresHardwareAuthToken);
+                }
+            } else {
+                Slog.d(getTag(), "Adding AIDL configs");
+                final List<SensorLocationInternal> workaroundLocations =
+                        getWorkaroundSensorProps(mContext);
+                for (SensorProps prop : props) {
+                    addAidlSensors(prop, gestureAvailabilityDispatcher, workaroundLocations,
+                            resetLockoutRequiresHardwareAuthToken);
                 }
             }
+        } else {
+            final List<SensorLocationInternal> workaroundLocations =
+                    getWorkaroundSensorProps(mContext);
 
-            final FingerprintSensorPropertiesInternal internalProp =
-                    new FingerprintSensorPropertiesInternal(prop.commonProps.sensorId,
-                            prop.commonProps.sensorStrength,
-                            prop.commonProps.maxEnrollmentsPerUser,
-                            componentInfo,
-                            prop.sensorType,
-                            prop.halControlsIllumination,
-                            true /* resetLockoutRequiresHardwareAuthToken */,
-                            !workaroundLocations.isEmpty() ? workaroundLocations :
-                                    Arrays.stream(prop.sensorLocations).map(location ->
-                                                    new SensorLocationInternal(
-                                                            location.display,
-                                                            location.sensorLocationX,
-                                                            location.sensorLocationY,
-                                                            location.sensorRadius))
-                                            .collect(Collectors.toList()));
-            final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
-                    internalProp, lockoutResetDispatcher, gestureAvailabilityDispatcher,
-                    mBiometricContext);
-            final int sessionUserId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
-                    sensor.getLazySession().get().getUserId();
-            mFingerprintSensors.addSensor(sensorId, sensor, sessionUserId,
-                    new SynchronousUserSwitchObserver() {
-                        @Override
-                        public void onUserSwitching(int newUserId) {
-                            scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
-                        }
-                    });
-            Slog.d(getTag(), "Added: " + internalProp);
+            for (SensorProps prop : props) {
+                final int sensorId = prop.commonProps.sensorId;
+                final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+                if (prop.commonProps.componentInfo != null) {
+                    for (ComponentInfo info : prop.commonProps.componentInfo) {
+                        componentInfo.add(new ComponentInfoInternal(info.componentId,
+                                info.hardwareVersion, info.firmwareVersion, info.serialNumber,
+                                info.softwareVersion));
+                    }
+                }
+                final FingerprintSensorPropertiesInternal internalProp =
+                        new FingerprintSensorPropertiesInternal(prop.commonProps.sensorId,
+                                prop.commonProps.sensorStrength,
+                                prop.commonProps.maxEnrollmentsPerUser,
+                                componentInfo,
+                                prop.sensorType,
+                                prop.halControlsIllumination,
+                                true /* resetLockoutRequiresHardwareAuthToken */,
+                                !workaroundLocations.isEmpty() ? workaroundLocations :
+                                        Arrays.stream(prop.sensorLocations).map(
+                                                        location -> new SensorLocationInternal(
+                                                                location.display,
+                                                                location.sensorLocationX,
+                                                                location.sensorLocationY,
+                                                                location.sensorRadius))
+                                                .collect(Collectors.toList()));
+                final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext,
+                        mHandler, internalProp, mLockoutResetDispatcher,
+                        gestureAvailabilityDispatcher, mBiometricContext);
+                sensor.init(gestureAvailabilityDispatcher,
+                        mLockoutResetDispatcher);
+                final int sessionUserId =
+                        sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+                                sensor.getLazySession().get().getUserId();
+                mFingerprintSensors.addSensor(sensorId, sensor, sessionUserId,
+                        new SynchronousUserSwitchObserver() {
+                            @Override
+                            public void onUserSwitching(int newUserId) {
+                                scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+                            }
+                        });
+                Slog.d(getTag(), "Added: " + mFingerprintSensors.get(sensorId).toString());
+            }
         }
     }
 
+    private void addHidlSensors(@NonNull SensorProps prop,
+            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            boolean resetLockoutRequiresHardwareAuthToken) {
+        final int sensorId = prop.commonProps.sensorId;
+        final Sensor sensor = new HidlToAidlSensorAdapter(getTag() + "/"
+                + sensorId, this, mContext, mHandler,
+                prop, mLockoutResetDispatcher, gestureAvailabilityDispatcher,
+                mBiometricContext, resetLockoutRequiresHardwareAuthToken,
+                () -> scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser(),
+                        null /* callback */));
+        sensor.init(gestureAvailabilityDispatcher, mLockoutResetDispatcher);
+        final int sessionUserId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+                sensor.getLazySession().get().getUserId();
+        mFingerprintSensors.addSensor(sensorId, sensor, sessionUserId,
+                new SynchronousUserSwitchObserver() {
+                    @Override
+                    public void onUserSwitching(int newUserId) {
+                        scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+                    }
+                });
+        Slog.d(getTag(), "Added: " + mFingerprintSensors.get(sensorId).toString());
+    }
+
+    private void addAidlSensors(@NonNull SensorProps prop,
+            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            List<SensorLocationInternal> workaroundLocations,
+            boolean resetLockoutRequiresHardwareAuthToken) {
+        final int sensorId = prop.commonProps.sensorId;
+        final Sensor sensor = new Sensor(getTag() + "/" + sensorId,
+                this, mContext, mHandler,
+                prop, mLockoutResetDispatcher, gestureAvailabilityDispatcher,
+                mBiometricContext, workaroundLocations,
+                resetLockoutRequiresHardwareAuthToken);
+        sensor.init(gestureAvailabilityDispatcher, mLockoutResetDispatcher);
+        final int sessionUserId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+                sensor.getLazySession().get().getUserId();
+        mFingerprintSensors.addSensor(sensorId, sensor, sessionUserId,
+                new SynchronousUserSwitchObserver() {
+                    @Override
+                    public void onUserSwitching(int newUserId) {
+                        scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+                    }
+                });
+        Slog.d(getTag(), "Added: " + mFingerprintSensors.get(sensorId).toString());
+    }
+
     private String getTag() {
         return "FingerprintProvider/" + mHalInstanceName;
     }
@@ -351,7 +432,10 @@
         }
     }
 
-    private void scheduleLoadAuthenticatorIdsForUser(int sensorId, int userId) {
+    /**
+     * Schedules FingerprintGetAuthenticatorIdClient for specific sensor and user.
+     */
+    protected void scheduleLoadAuthenticatorIdsForUser(int sensorId, int userId) {
         mHandler.post(() -> {
             final FingerprintGetAuthenticatorIdClient client =
                     new FingerprintGetAuthenticatorIdClient(mContext,
@@ -387,8 +471,8 @@
                             BiometricsProtoEnums.CLIENT_UNKNOWN,
                             mAuthenticationStatsCollector),
                     mBiometricContext, hardwareAuthToken,
-                    mFingerprintSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher,
-                    Utils.getCurrentStrength(sensorId));
+                    mFingerprintSensors.get(sensorId).getLockoutTracker(false /* forAuth */),
+                    mLockoutResetDispatcher, Utils.getCurrentStrength(sensorId));
             scheduleForSensor(sensorId, client);
         });
     }
@@ -443,18 +527,23 @@
                     mFingerprintSensors.get(sensorId).getSensorProperties(),
                     mUdfpsOverlayController, mSidefpsController,
                     mAuthenticationStateListeners, maxTemplatesPerUser, enrollReason);
-            scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
-                    mBiometricStateCallback, new ClientMonitorCallback() {
-                @Override
-                public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
-                        boolean success) {
-                    ClientMonitorCallback.super.onClientFinished(clientMonitor, success);
-                    if (success) {
-                        scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
-                        scheduleInvalidationRequest(sensorId, userId);
-                    }
-                }
-            }));
+            if (Flags.deHidl()) {
+                scheduleForSensor(sensorId, client, mBiometricStateCallback);
+            } else {
+                scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
+                        mBiometricStateCallback, new ClientMonitorCallback() {
+                            @Override
+                            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+                                    boolean success) {
+                                ClientMonitorCallback.super.onClientFinished(
+                                        clientMonitor, success);
+                                if (success) {
+                                    scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
+                                    scheduleInvalidationRequest(sensorId, userId);
+                                }
+                            }
+                        }));
+            }
         });
         return id;
     }
@@ -497,6 +586,13 @@
             final int userId = options.getUserId();
             final int sensorId = options.getSensorId();
             final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
+            final LockoutTracker lockoutTracker;
+            if (Flags.deHidl()) {
+                lockoutTracker = mFingerprintSensors.get(sensorId)
+                        .getLockoutTracker(true /* forAuth */);
+            } else {
+                lockoutTracker = null;
+            }
             final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
                     mContext, mFingerprintSensors.get(sensorId).getLazySession(), token, requestId,
                     callback, operationId, restricted, options, cookie,
@@ -510,7 +606,7 @@
                     mFingerprintSensors.get(sensorId).getSensorProperties(), mHandler,
                     Utils.getCurrentStrength(sensorId),
                     SystemClock.elapsedRealtimeClock(),
-                    null /* lockoutTracker */);
+                    lockoutTracker);
             scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
 
                 @Override
@@ -636,6 +732,9 @@
 
     @Override
     public boolean isHardwareDetected(int sensorId) {
+        if (Flags.deHidl()) {
+            return mFingerprintSensors.get(sensorId).isHardwareDetected(mHalInstanceName);
+        }
         return hasHalInstance();
     }
 
@@ -674,8 +773,12 @@
 
     @Override
     public int getLockoutModeForUser(int sensorId, int userId) {
-        return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
-                Utils.getCurrentStrength(sensorId));
+        if (Flags.deHidl()) {
+            return mFingerprintSensors.get(sensorId).getLockoutModeForUser(userId);
+        } else {
+            return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
+                    Utils.getCurrentStrength(sensorId));
+        }
     }
 
     @Override
@@ -829,6 +932,10 @@
         mTestHalEnabled = enabled;
     }
 
+    public boolean getTestHalEnabled() {
+        return mTestHalEnabled;
+    }
+
     // TODO(b/174868353): workaround for gaps in HAL interface (remove and get directly from HAL)
     // reads values via an overlay instead of querying the HAL
     @NonNull
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
index ec225a6..387ae12 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
@@ -60,7 +60,8 @@
             @Authenticators.Types int biometricStrength) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
                 0 /* cookie */, sensorId, biometricLogger, biometricContext);
-        mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
+        mHardwareAuthToken = hardwareAuthToken == null ? null :
+                HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
         mLockoutCache = lockoutTracker;
         mLockoutResetDispatcher = lockoutResetDispatcher;
         mBiometricStrength = biometricStrength;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index 893cb8f..dd887bb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -21,21 +21,28 @@
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.ITestSession;
 import android.hardware.biometrics.ITestSessionCallback;
+import android.hardware.biometrics.SensorLocationInternal;
+import android.hardware.biometrics.common.ComponentInfo;
+import android.hardware.biometrics.fingerprint.IFingerprint;
 import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.biometrics.fingerprint.SensorProps;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.SensorServiceStateProto;
 import com.android.server.biometrics.SensorStateProto;
 import com.android.server.biometrics.UserStateProto;
@@ -48,15 +55,20 @@
 import com.android.server.biometrics.sensors.ErrorConsumer;
 import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.StartUserClient;
 import com.android.server.biometrics.sensors.StopUserClient;
 import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
 import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
 
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
 /**
  * Maintains the state of a single sensor within an instance of the
@@ -73,15 +85,17 @@
     @NonNull private final IBinder mToken;
     @NonNull private final Handler mHandler;
     @NonNull private final FingerprintSensorPropertiesInternal mSensorProperties;
-    @NonNull private final UserAwareBiometricScheduler mScheduler;
-    @NonNull private final LockoutCache mLockoutCache;
+    @NonNull private BiometricScheduler mScheduler;
+    @NonNull private LockoutTracker mLockoutTracker;
     @NonNull private final Map<Integer, Long> mAuthenticatorIds;
+    @NonNull private final BiometricContext mBiometricContext;
 
     @Nullable AidlSession mCurrentSession;
-    @NonNull private final Supplier<AidlSession> mLazySession;
+    @NonNull private Supplier<AidlSession> mLazySession;
 
-    Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
-            @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
+    public Sensor(@NonNull String tag, @NonNull FingerprintProvider provider,
+            @NonNull Context context, @NonNull Handler handler,
+            @NonNull FingerprintSensorPropertiesInternal sensorProperties,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
             @NonNull BiometricContext biometricContext, AidlSession session) {
@@ -91,8 +105,38 @@
         mToken = new Binder();
         mHandler = handler;
         mSensorProperties = sensorProperties;
-        mLockoutCache = new LockoutCache();
-        mScheduler = new UserAwareBiometricScheduler(tag,
+        mBiometricContext = biometricContext;
+        mAuthenticatorIds = new HashMap<>();
+        mCurrentSession = session;
+    }
+
+    Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
+            @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            @NonNull BiometricContext biometricContext) {
+        this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher,
+                gestureAvailabilityDispatcher, biometricContext, null);
+    }
+
+    Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
+            @NonNull Handler handler, @NonNull SensorProps sensorProp,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            @NonNull BiometricContext biometricContext,
+            @NonNull List<SensorLocationInternal> workaroundLocation,
+            boolean resetLockoutRequiresHardwareAuthToken) {
+        this(tag, provider, context, handler, getFingerprintSensorPropertiesInternal(sensorProp,
+                        workaroundLocation, resetLockoutRequiresHardwareAuthToken),
+                lockoutResetDispatcher, gestureAvailabilityDispatcher, biometricContext, null);
+    }
+
+    /**
+     * Initialize biometric scheduler, lockout tracker and session for the sensor.
+     */
+    public void init(GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            LockoutResetDispatcher lockoutResetDispatcher) {
+        mScheduler = new UserAwareBiometricScheduler(mTag,
                 BiometricScheduler.sensorTypeFromFingerprintProperties(mSensorProperties),
                 gestureAvailabilityDispatcher,
                 () -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL,
@@ -102,7 +146,7 @@
                     public StopUserClient<?> getStopUserClient(int userId) {
                         return new FingerprintStopUserClient(mContext, mLazySession, mToken,
                                 userId, mSensorProperties.sensorId,
-                                BiometricLogger.ofUnknown(mContext), biometricContext,
+                                BiometricLogger.ofUnknown(mContext), mBiometricContext,
                                 () -> mCurrentSession = null);
                     }
 
@@ -111,13 +155,38 @@
                     public StartUserClient<?, ?> getStartUserClient(int newUserId) {
                         final int sensorId = mSensorProperties.sensorId;
 
-                        final AidlResponseHandler resultController = new AidlResponseHandler(
-                                mContext, mScheduler, sensorId, newUserId,
-                                mLockoutCache, lockoutResetDispatcher,
-                                biometricContext.getAuthSessionCoordinator(), () -> {
-                            Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
-                            mCurrentSession = null;
-                        });
+                        final AidlResponseHandler resultController;
+
+                        if (Flags.deHidl()) {
+                            resultController = new AidlResponseHandler(
+                                    mContext, mScheduler, sensorId, newUserId,
+                                    mLockoutTracker, lockoutResetDispatcher,
+                                    mBiometricContext.getAuthSessionCoordinator(), () -> {},
+                                    new AidlResponseHandler.AidlResponseHandlerCallback() {
+                                        @Override
+                                        public void onEnrollSuccess() {
+                                            mProvider.scheduleLoadAuthenticatorIdsForUser(sensorId,
+                                                    newUserId);
+                                            mProvider.scheduleInvalidationRequest(sensorId,
+                                                    newUserId);
+                                        }
+
+                                        @Override
+                                        public void onHardwareUnavailable() {
+                                            Slog.e(mTag,
+                                                    "Fingerprint sensor hardware unavailable.");
+                                            mCurrentSession = null;
+                                        }
+                                    });
+                        } else {
+                            resultController = new AidlResponseHandler(
+                                    mContext, mScheduler, sensorId, newUserId,
+                                    mLockoutTracker, lockoutResetDispatcher,
+                                    mBiometricContext.getAuthSessionCoordinator(), () -> {
+                                Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
+                                mCurrentSession = null;
+                            });
+                        }
 
                         final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
                                 (userIdStarted, newSession, halInterfaceVersion) -> {
@@ -133,40 +202,58 @@
                                                         + "sensor: "
                                                         + sensorId
                                                         + ", user: " + userIdStarted);
-                                        provider.scheduleInvalidationRequest(sensorId,
+                                        mProvider.scheduleInvalidationRequest(sensorId,
                                                 userIdStarted);
                                     }
                                 };
 
-                        return new FingerprintStartUserClient(mContext, provider::getHalInstance,
+                        return new FingerprintStartUserClient(mContext, mProvider::getHalInstance,
                                 mToken, newUserId, mSensorProperties.sensorId,
-                                BiometricLogger.ofUnknown(mContext), biometricContext,
+                                BiometricLogger.ofUnknown(mContext), mBiometricContext,
                                 resultController, userStartedCallback);
                     }
                 });
-        mAuthenticatorIds = new HashMap<>();
+        mLockoutTracker = new LockoutCache();
         mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
-        mCurrentSession = session;
     }
 
-    Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
-            @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
-            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
-            @NonNull BiometricContext biometricContext) {
-        this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher,
-                gestureAvailabilityDispatcher, biometricContext, null);
+    protected static FingerprintSensorPropertiesInternal getFingerprintSensorPropertiesInternal(
+            SensorProps prop, List<SensorLocationInternal> workaroundLocations,
+            boolean resetLockoutRequiresHardwareAuthToken) {
+        final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+        if (prop.commonProps.componentInfo != null) {
+            for (ComponentInfo info : prop.commonProps.componentInfo) {
+                componentInfo.add(new ComponentInfoInternal(info.componentId,
+                        info.hardwareVersion, info.firmwareVersion, info.serialNumber,
+                        info.softwareVersion));
+            }
+        }
+        return new FingerprintSensorPropertiesInternal(prop.commonProps.sensorId,
+                prop.commonProps.sensorStrength,
+                prop.commonProps.maxEnrollmentsPerUser,
+                componentInfo,
+                prop.sensorType,
+                prop.halControlsIllumination,
+                resetLockoutRequiresHardwareAuthToken,
+                !workaroundLocations.isEmpty() ? workaroundLocations :
+                        Arrays.stream(prop.sensorLocations).map(location ->
+                                        new SensorLocationInternal(
+                                                location.display,
+                                                location.sensorLocationX,
+                                                location.sensorLocationY,
+                                                location.sensorRadius))
+                                .collect(Collectors.toList()));
     }
 
-    @NonNull Supplier<AidlSession> getLazySession() {
+    @NonNull public Supplier<AidlSession> getLazySession() {
         return mLazySession;
     }
 
-    @NonNull FingerprintSensorPropertiesInternal getSensorProperties() {
+    @NonNull public FingerprintSensorPropertiesInternal getSensorProperties() {
         return mSensorProperties;
     }
 
-    @Nullable AidlSession getSessionForUser(int userId) {
+    @Nullable protected AidlSession getSessionForUser(int userId) {
         if (mCurrentSession != null && mCurrentSession.getUserId() == userId) {
             return mCurrentSession;
         } else {
@@ -180,15 +267,18 @@
                 biometricStateCallback, mProvider, this);
     }
 
-    @NonNull BiometricScheduler getScheduler() {
+    @NonNull public BiometricScheduler getScheduler() {
         return mScheduler;
     }
 
-    @NonNull LockoutCache getLockoutCache() {
-        return mLockoutCache;
+    @NonNull protected LockoutTracker getLockoutTracker(boolean forAuth) {
+        if (forAuth) {
+            return null;
+        }
+        return mLockoutTracker;
     }
 
-    @NonNull Map<Integer, Long> getAuthenticatorIds() {
+    @NonNull public Map<Integer, Long> getAuthenticatorIds() {
         return mAuthenticatorIds;
     }
 
@@ -262,4 +352,49 @@
         mScheduler.reset();
         mCurrentSession = null;
     }
+
+    @NonNull protected Handler getHandler() {
+        return mHandler;
+    }
+
+    @NonNull protected Context getContext() {
+        return mContext;
+    }
+
+    /**
+     * Returns true if the sensor hardware is detected.
+     */
+    protected boolean isHardwareDetected(String halInstance) {
+        if (mTestHalEnabled) {
+            return true;
+        }
+        return (ServiceManager.checkService(IFingerprint.DESCRIPTOR + "/" + halInstance)
+                != null);
+    }
+
+    @NonNull protected BiometricContext getBiometricContext() {
+        return mBiometricContext;
+    }
+
+    /**
+     * Returns lockout mode of this sensor.
+     */
+    @LockoutTracker.LockoutMode
+    public int getLockoutModeForUser(int userId) {
+        return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
+                Utils.getCurrentStrength(mSensorProperties.sensorId));
+    }
+
+    public void setScheduler(BiometricScheduler scheduler) {
+        mScheduler = scheduler;
+    }
+
+    public void setLazySession(
+            Supplier<AidlSession> lazySession) {
+        mLazySession = lazySession;
+    }
+
+    public FingerprintProvider getProvider() {
+        return mProvider;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
index a4e6025..5c5b992 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
@@ -29,7 +29,8 @@
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.HalClientMonitor;
+import com.android.server.biometrics.sensors.StartUserClient;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession;
 
 import java.io.File;
 import java.util.Map;
@@ -38,7 +39,8 @@
 /**
  * Sets the HAL's current active user, and updates the framework's authenticatorId cache.
  */
-public class FingerprintUpdateActiveUserClient extends HalClientMonitor<IBiometricsFingerprint> {
+public class FingerprintUpdateActiveUserClient extends
+        StartUserClient<IBiometricsFingerprint, AidlSession> {
 
     private static final String TAG = "FingerprintUpdateActiveUserClient";
     private static final String FP_DATA_DIR = "fpdata";
@@ -53,11 +55,24 @@
             @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
             @NonNull String owner, int sensorId,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
-            Supplier<Integer> currentUserId,
+            @NonNull Supplier<Integer> currentUserId,
             boolean hasEnrolledBiometrics, @NonNull Map<Integer, Long> authenticatorIds,
             boolean forceUpdateAuthenticatorId) {
-        super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
-                0 /* cookie */, sensorId, logger, biometricContext);
+        this(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext, currentUserId,
+                hasEnrolledBiometrics, authenticatorIds, forceUpdateAuthenticatorId,
+                (newUserId, newUser, halInterfaceVersion) -> {});
+    }
+
+    FingerprintUpdateActiveUserClient(@NonNull Context context,
+            @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
+            @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            @NonNull Supplier<Integer> currentUserId,
+            boolean hasEnrolledBiometrics, @NonNull Map<Integer, Long> authenticatorIds,
+            boolean forceUpdateAuthenticatorId,
+            @NonNull UserStartedCallback<AidlSession> userStartedCallback) {
+        super(context, lazyDaemon, null /* token */, userId, sensorId, logger, biometricContext,
+                userStartedCallback);
         mCurrentUserId = currentUserId;
         mForceUpdateAuthenticatorId = forceUpdateAuthenticatorId;
         mHasEnrolledBiometrics = hasEnrolledBiometrics;
@@ -70,6 +85,7 @@
 
         if (mCurrentUserId.get() == getTargetUserId() && !mForceUpdateAuthenticatorId) {
             Slog.d(TAG, "Already user: " + mCurrentUserId + ", returning");
+            mUserStartedCallback.onUserStarted(getTargetUserId(), null, 0);
             callback.onClientFinished(this, true /* success */);
             return;
         }
@@ -119,6 +135,7 @@
             getFreshDaemon().setActiveGroup(targetId, mDirectory.getAbsolutePath());
             mAuthenticatorIds.put(targetId, mHasEnrolledBiometrics
                     ? getFreshDaemon().getAuthenticatorId() : 0L);
+            mUserStartedCallback.onUserStarted(targetId, null, 0);
             mCallback.onClientFinished(this, true /* success */);
         } catch (RemoteException e) {
             Slog.e(TAG, "Failed to setActiveGroup: " + e);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java
index c3e5cbe..e9a48e7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java
@@ -20,6 +20,7 @@
 import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback;
 
 import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
 
 import java.util.ArrayList;
@@ -73,12 +74,12 @@
 
     @Override
     public void onRemoved(long deviceId, int fingerId, int groupId, int remaining) {
-        mAidlResponseHandler.onEnrollmentsRemoved(new int[]{fingerId});
+        mAidlResponseHandler.onEnrollmentRemoved(fingerId, remaining);
     }
 
     @Override
     public void onEnumerate(long deviceId, int fingerId, int groupId, int remaining) {
-        mAidlResponseHandler.onEnrollmentsEnumerated(new int[]{fingerId});
+        mAidlResponseHandler.onEnrollmentEnumerated(fingerId, remaining);
     }
 
     void onChallengeGenerated(long challenge) {
@@ -92,4 +93,8 @@
     void onResetLockout() {
         mAidlResponseHandler.onLockoutCleared();
     }
+
+    <T extends BaseClientMonitor> void unsupportedClientScheduled(Class<T> className) {
+        mAidlResponseHandler.onUnsupportedClientScheduled(className);
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
new file mode 100644
index 0000000..0bb61415
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
@@ -0,0 +1,297 @@
+/*
+ * 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.biometrics.sensors.fingerprint.hidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.hardware.biometrics.fingerprint.SensorProps;
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.os.Handler;
+import android.os.IHwBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.StartUserClient;
+import com.android.server.biometrics.sensors.StopUserClient;
+import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
+import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider;
+import com.android.server.biometrics.sensors.fingerprint.aidl.Sensor;
+
+import java.util.ArrayList;
+
+/**
+ * Convert HIDL sensor configurations to an AIDL Sensor.
+ */
+public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRecipient {
+    private static final String TAG = "HidlToAidlSensorAdapter";
+
+    private final Runnable mInternalCleanupRunnable;
+    private final LockoutResetDispatcher mLockoutResetDispatcher;
+    private LockoutFrameworkImpl mLockoutTracker;
+    private final AuthSessionCoordinator mAuthSessionCoordinator;
+    private final AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+    private int mCurrentUserId = UserHandle.USER_NULL;
+    private IBiometricsFingerprint mDaemon;
+    private AidlSession mSession;
+
+    private final StartUserClient.UserStartedCallback<AidlSession> mUserStartedCallback =
+            (newUserId, newUser, halInterfaceVersion) -> {
+                if (mCurrentUserId != newUserId) {
+                    handleUserChanged(newUserId);
+                }
+            };
+
+    public HidlToAidlSensorAdapter(@NonNull String tag, @NonNull FingerprintProvider provider,
+            @NonNull Context context, @NonNull Handler handler,
+            @NonNull SensorProps prop,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            @NonNull BiometricContext biometricContext,
+            boolean resetLockoutRequiresHardwareAuthToken,
+            @NonNull Runnable internalCleanupRunnable) {
+        this(tag, provider, context, handler, prop, lockoutResetDispatcher,
+                gestureAvailabilityDispatcher, biometricContext,
+                resetLockoutRequiresHardwareAuthToken, internalCleanupRunnable,
+                new AuthSessionCoordinator(), null /* daemon */,
+                null /* onEnrollSuccessCallback */);
+    }
+
+    @VisibleForTesting
+    HidlToAidlSensorAdapter(@NonNull String tag, @NonNull FingerprintProvider provider,
+            @NonNull Context context, @NonNull Handler handler,
+            @NonNull SensorProps prop,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            @NonNull BiometricContext biometricContext,
+            boolean resetLockoutRequiresHardwareAuthToken,
+            @NonNull Runnable internalCleanupRunnable,
+            @NonNull AuthSessionCoordinator authSessionCoordinator,
+            @Nullable IBiometricsFingerprint daemon,
+            @Nullable AidlResponseHandler.AidlResponseHandlerCallback aidlResponseHandlerCallback) {
+        super(tag, provider, context, handler, getFingerprintSensorPropertiesInternal(prop,
+                        new ArrayList<>(), resetLockoutRequiresHardwareAuthToken),
+                lockoutResetDispatcher,
+                gestureAvailabilityDispatcher,
+                biometricContext, null /* session */);
+        mLockoutResetDispatcher = lockoutResetDispatcher;
+        mInternalCleanupRunnable = internalCleanupRunnable;
+        mAuthSessionCoordinator = authSessionCoordinator;
+        mDaemon = daemon;
+        mAidlResponseHandlerCallback = aidlResponseHandlerCallback == null
+                ? new AidlResponseHandler.AidlResponseHandlerCallback() {
+                    @Override
+                    public void onEnrollSuccess() {
+                        getScheduler()
+                                .scheduleClientMonitor(getFingerprintUpdateActiveUserClient(
+                                        mCurrentUserId, true /* forceUpdateAuthenticatorIds */));
+                    }
+
+                    @Override
+                    public void onHardwareUnavailable() {
+                        mDaemon = null;
+                        mSession = null;
+                        mCurrentUserId = UserHandle.USER_NULL;
+                    }
+                } : aidlResponseHandlerCallback;
+    }
+
+    @Override
+    public void serviceDied(long cookie) {
+        Slog.d(TAG, "HAL died.");
+        mSession = null;
+        mDaemon = null;
+    }
+
+    @Override
+    @LockoutTracker.LockoutMode
+    public int getLockoutModeForUser(int userId) {
+        return mLockoutTracker.getLockoutModeForUser(userId);
+    }
+
+    @Override
+    public void init(GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            LockoutResetDispatcher lockoutResetDispatcher) {
+        setLazySession(this::getSession);
+        setScheduler(new UserAwareBiometricScheduler(TAG,
+                BiometricScheduler.sensorTypeFromFingerprintProperties(getSensorProperties()),
+                gestureAvailabilityDispatcher, () -> mCurrentUserId, getUserSwitchCallback()));
+        mLockoutTracker = new LockoutFrameworkImpl(getContext(),
+                userId -> mLockoutResetDispatcher.notifyLockoutResetCallbacks(
+                        getSensorProperties().sensorId));
+    }
+
+    @Override
+    @Nullable
+    protected AidlSession getSessionForUser(int userId) {
+        if (mSession != null && mSession.getUserId() == userId) {
+            return mSession;
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    protected boolean isHardwareDetected(String halInstance) {
+        return getIBiometricsFingerprint() != null;
+    }
+
+    @NonNull
+    @Override
+    protected LockoutTracker getLockoutTracker(boolean forAuth) {
+        return mLockoutTracker;
+    }
+
+    private synchronized AidlSession getSession() {
+        if (mSession != null && mDaemon != null) {
+            return mSession;
+        } else {
+            return mSession = new AidlSession(this::getIBiometricsFingerprint,
+                    mCurrentUserId, getAidlResponseHandler());
+        }
+    }
+
+    private AidlResponseHandler getAidlResponseHandler() {
+        return new AidlResponseHandler(getContext(),
+                getScheduler(),
+                getSensorProperties().sensorId,
+                mCurrentUserId,
+                mLockoutTracker,
+                mLockoutResetDispatcher,
+                mAuthSessionCoordinator,
+                () -> {}, mAidlResponseHandlerCallback);
+    }
+
+    @VisibleForTesting IBiometricsFingerprint getIBiometricsFingerprint() {
+        if (getProvider().getTestHalEnabled()) {
+            final TestHal testHal = new TestHal(getContext(), getSensorProperties().sensorId);
+            testHal.setNotify(new HidlToAidlCallbackConverter(getAidlResponseHandler()));
+            return testHal;
+        }
+
+        if (mDaemon != null) {
+            return mDaemon;
+        }
+
+        try {
+            mDaemon = IBiometricsFingerprint.getService();
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to get fingerprint HAL", e);
+        } catch (java.util.NoSuchElementException e) {
+            // Service doesn't exist or cannot be opened.
+            Slog.w(TAG, "NoSuchElementException", e);
+        }
+
+        if (mDaemon == null) {
+            Slog.w(TAG, "Fingerprint HAL not available");
+            return null;
+        }
+
+        mDaemon.asBinder().linkToDeath(this, 0 /* flags */);
+
+        Slog.d(TAG, "Fingerprint HAL ready");
+
+        scheduleLoadAuthenticatorIds();
+        mInternalCleanupRunnable.run();
+        return mDaemon;
+    }
+
+    private UserAwareBiometricScheduler.UserSwitchCallback getUserSwitchCallback() {
+        return new UserAwareBiometricScheduler.UserSwitchCallback() {
+            @NonNull
+            @Override
+            public StopUserClient<?> getStopUserClient(int userId) {
+                return new StopUserClient<IBiometricsFingerprint>(getContext(),
+                        HidlToAidlSensorAdapter.this::getIBiometricsFingerprint,
+                        null /* token */, userId, getSensorProperties().sensorId,
+                        BiometricLogger.ofUnknown(getContext()), getBiometricContext(),
+                        () -> {
+                            mCurrentUserId = UserHandle.USER_NULL;
+                            mSession = null;
+                        }) {
+                    @Override
+                    public void start(@NonNull ClientMonitorCallback callback) {
+                        super.start(callback);
+                        startHalOperation();
+                    }
+
+                    @Override
+                    protected void startHalOperation() {
+                        onUserStopped();
+                    }
+
+                    @Override
+                    public void unableToStart() {
+                        getCallback().onClientFinished(this, false /* success */);
+                    }
+                };
+            }
+
+            @NonNull
+            @Override
+            public StartUserClient<?, ?> getStartUserClient(int newUserId) {
+                return getFingerprintUpdateActiveUserClient(newUserId,
+                        false /* forceUpdateAuthenticatorId */);
+            }
+        };
+    }
+
+    private FingerprintUpdateActiveUserClient getFingerprintUpdateActiveUserClient(int newUserId,
+            boolean forceUpdateAuthenticatorIds) {
+        return new FingerprintUpdateActiveUserClient(getContext(),
+                this::getIBiometricsFingerprint, newUserId, TAG,
+                getSensorProperties().sensorId, BiometricLogger.ofUnknown(getContext()),
+                getBiometricContext(), () -> mCurrentUserId,
+                !FingerprintUtils.getInstance(getSensorProperties().sensorId)
+                        .getBiometricsForUser(getContext(),
+                newUserId).isEmpty(), getAuthenticatorIds(), forceUpdateAuthenticatorIds,
+                mUserStartedCallback);
+    }
+
+    private void scheduleLoadAuthenticatorIds() {
+        getHandler().post(() -> {
+            for (UserInfo user : UserManager.get(getContext()).getAliveUsers()) {
+                final int targetUserId = user.id;
+                if (!getAuthenticatorIds().containsKey(targetUserId)) {
+                    getScheduler().scheduleClientMonitor(getFingerprintUpdateActiveUserClient(
+                            targetUserId, true /* forceUpdateAuthenticatorIds */));
+                }
+            }
+        });
+    }
+
+    @VisibleForTesting void handleUserChanged(int newUserId) {
+        Slog.d(TAG, "User changed. Current user is " + newUserId);
+        mSession = null;
+        mCurrentUserId = newUserId;
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapter.java
similarity index 73%
rename from services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java
rename to services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapter.java
index b48d232..2fc00e1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapter.java
@@ -25,20 +25,25 @@
 import android.hardware.keymaster.HardwareAuthToken;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.Log;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
 import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintGetAuthenticatorIdClient;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintInvalidationClient;
 
 import java.util.function.Supplier;
 
 /**
- * Adapter to convert AIDL-specific interface {@link ISession} methods to HIDL implementation.
+ * Adapter to convert HIDL methods into AIDL interface {@link ISession}.
  */
-public class AidlToHidlAdapter implements ISession {
-    private final String TAG = "AidlToHidlAdapter";
+public class HidlToAidlSessionAdapter implements ISession {
+
+    private final String TAG = "HidlToAidlSessionAdapter";
+
     @VisibleForTesting
     static final int ENROLL_TIMEOUT_SEC = 60;
     @NonNull
@@ -46,22 +51,13 @@
     private final int mUserId;
     private HidlToAidlCallbackConverter mHidlToAidlCallbackConverter;
 
-    public AidlToHidlAdapter(Supplier<IBiometricsFingerprint> session, int userId,
+    public HidlToAidlSessionAdapter(Supplier<IBiometricsFingerprint> session, int userId,
             AidlResponseHandler aidlResponseHandler) {
         mSession = session;
         mUserId = userId;
         setCallback(aidlResponseHandler);
     }
 
-    private void setCallback(AidlResponseHandler aidlResponseHandler) {
-        mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler);
-        try {
-            mSession.get().setNotify(mHidlToAidlCallbackConverter);
-        } catch (RemoteException e) {
-            Slog.d(TAG, "Failed to set callback");
-        }
-    }
-
     @Override
     public IBinder asBinder() {
         return null;
@@ -125,12 +121,16 @@
 
     @Override
     public void getAuthenticatorId() throws RemoteException {
-        //Unsupported in HIDL
+        Log.e(TAG, "getAuthenticatorId unsupported in HIDL");
+        mHidlToAidlCallbackConverter.unsupportedClientScheduled(
+                FingerprintGetAuthenticatorIdClient.class);
     }
 
     @Override
     public void invalidateAuthenticatorId() throws RemoteException {
-        //Unsupported in HIDL
+        Log.e(TAG, "invalidateAuthenticatorId unsupported in HIDL");
+        mHidlToAidlCallbackConverter.unsupportedClientScheduled(
+                FingerprintInvalidationClient.class);
     }
 
     @Override
@@ -140,72 +140,92 @@
 
     @Override
     public void close() throws RemoteException {
-        //Unsupported in HIDL
+        Log.e(TAG, "close unsupported in HIDL");
     }
 
     @Override
     public void onUiReady() throws RemoteException {
-        //Unsupported in HIDL
+        Log.e(TAG, "onUiReady unsupported in HIDL");
     }
 
     @Override
     public ICancellationSignal authenticateWithContext(long operationId, OperationContext context)
             throws RemoteException {
-        //Unsupported in HIDL
-        return null;
+        Log.e(TAG, "authenticateWithContext unsupported in HIDL");
+        return authenticate(operationId);
     }
 
     @Override
     public ICancellationSignal enrollWithContext(HardwareAuthToken hat, OperationContext context)
             throws RemoteException {
-        //Unsupported in HIDL
-        return null;
+        Log.e(TAG, "enrollWithContext unsupported in HIDL");
+        return enroll(hat);
     }
 
     @Override
     public ICancellationSignal detectInteractionWithContext(OperationContext context)
             throws RemoteException {
-        //Unsupported in HIDL
-        return null;
+        Log.e(TAG, "enrollWithContext unsupported in HIDL");
+        return detectInteraction();
     }
 
     @Override
     public void onPointerDownWithContext(PointerContext context) throws RemoteException {
-        //Unsupported in HIDL
+        Log.e(TAG, "onPointerDownWithContext unsupported in HIDL");
+        onPointerDown(context.pointerId, (int) context.x, (int) context.y, context.minor,
+                context.major);
     }
 
     @Override
     public void onPointerUpWithContext(PointerContext context) throws RemoteException {
-        //Unsupported in HIDL
+        Log.e(TAG, "onPointerUpWithContext unsupported in HIDL");
+        onPointerUp(context.pointerId);
     }
 
     @Override
     public void onContextChanged(OperationContext context) throws RemoteException {
-        //Unsupported in HIDL
+        Log.e(TAG, "onContextChanged unsupported in HIDL");
     }
 
     @Override
     public void onPointerCancelWithContext(PointerContext context) throws RemoteException {
-        //Unsupported in HIDL
+        Log.e(TAG, "onPointerCancelWithContext unsupported in HIDL");
     }
 
     @Override
     public void setIgnoreDisplayTouches(boolean shouldIgnore) throws RemoteException {
-        //Unsupported in HIDL
+        Log.e(TAG, "setIgnoreDisplayTouches unsupported in HIDL");
     }
 
     @Override
     public int getInterfaceVersion() throws RemoteException {
-        //Unsupported in HIDL
+        Log.e(TAG, "getInterfaceVersion unsupported in HIDL");
         return 0;
     }
 
     @Override
     public String getInterfaceHash() throws RemoteException {
-        //Unsupported in HIDL
+        Log.e(TAG, "getInterfaceHash unsupported in HIDL");
         return null;
     }
 
+    private void setCallback(AidlResponseHandler aidlResponseHandler) {
+        mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler);
+        try {
+            if (mSession.get() != null) {
+                long halId = mSession.get().setNotify(mHidlToAidlCallbackConverter);
+                Slog.d(TAG, "Fingerprint HAL ready, HAL ID: " + halId);
+                if (halId == 0) {
+                    Slog.d(TAG, "Unable to set HIDL callback.");
+                }
+            } else {
+                Slog.e(TAG, "Unable to set HIDL callback. HIDL daemon is null.");
+            }
+        } catch (RemoteException e) {
+            Slog.d(TAG, "Failed to set callback");
+        }
+    }
+
     private class Cancellation extends ICancellationSignal.Stub {
 
         Cancellation() {}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
index 0730c67..2f77275 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT;
 
+import android.annotation.NonNull;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
@@ -31,15 +32,18 @@
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.sensors.LockoutTracker;
 
+import java.util.function.Function;
+
 /**
  * Tracks and enforces biometric lockout for biometric sensors that do not support lockout in the
  * HAL.
  */
 public class LockoutFrameworkImpl implements LockoutTracker {
 
-    private static final String TAG = "LockoutTracker";
+    private static final String TAG = "LockoutFrameworkImpl";
     private static final String ACTION_LOCKOUT_RESET =
             "com.android.server.biometrics.sensors.fingerprint.ACTION_LOCKOUT_RESET";
     private static final int MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED = 5;
@@ -65,22 +69,32 @@
         void onLockoutReset(int userId);
     }
 
-    private final Context mContext;
     private final LockoutResetCallback mLockoutResetCallback;
     private final SparseBooleanArray mTimedLockoutCleared;
     private final SparseIntArray mFailedAttempts;
     private final AlarmManager mAlarmManager;
     private final LockoutReceiver mLockoutReceiver;
     private final Handler mHandler;
+    private final Function<Integer, PendingIntent> mLockoutResetIntent;
 
-    public LockoutFrameworkImpl(Context context, LockoutResetCallback lockoutResetCallback) {
-        mContext = context;
+    public LockoutFrameworkImpl(@NonNull Context context,
+            @NonNull LockoutResetCallback lockoutResetCallback) {
+        this(context, lockoutResetCallback, (userId) -> PendingIntent.getBroadcast(context, userId,
+                new Intent(ACTION_LOCKOUT_RESET).putExtra(KEY_LOCKOUT_RESET_USER, userId),
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE));
+    }
+
+    @VisibleForTesting
+    LockoutFrameworkImpl(@NonNull Context context,
+            @NonNull LockoutResetCallback lockoutResetCallback,
+            @NonNull Function<Integer, PendingIntent> lockoutResetIntent) {
         mLockoutResetCallback = lockoutResetCallback;
         mTimedLockoutCleared = new SparseBooleanArray();
         mFailedAttempts = new SparseIntArray();
         mAlarmManager = context.getSystemService(AlarmManager.class);
         mLockoutReceiver = new LockoutReceiver();
         mHandler = new Handler(Looper.getMainLooper());
+        mLockoutResetIntent = lockoutResetIntent;
 
         context.registerReceiver(mLockoutReceiver, new IntentFilter(ACTION_LOCKOUT_RESET),
                 RESET_FINGERPRINT_LOCKOUT, null /* handler */, Context.RECEIVER_EXPORTED);
@@ -129,34 +143,18 @@
         return LOCKOUT_NONE;
     }
 
-    /**
-     * Clears lockout for Fingerprint HIDL HAL
-     */
     @Override
-    public void setLockoutModeForUser(int userId, int mode) {
-        mFailedAttempts.put(userId, 0);
-        mTimedLockoutCleared.put(userId, true);
-        // If we're asked to reset failed attempts externally (i.e. from Keyguard),
-        // the alarm might still be pending; remove it.
-        cancelLockoutResetForUser(userId);
-        mLockoutResetCallback.onLockoutReset(userId);
-    }
+    public void setLockoutModeForUser(int userId, int mode) {}
 
     private void cancelLockoutResetForUser(int userId) {
-        mAlarmManager.cancel(getLockoutResetIntentForUser(userId));
+        mAlarmManager.cancel(mLockoutResetIntent.apply(userId));
     }
 
     private void scheduleLockoutResetForUser(int userId) {
         mHandler.post(() -> {
             mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                SystemClock.elapsedRealtime() + FAIL_LOCKOUT_TIMEOUT_MS,
-                getLockoutResetIntentForUser(userId));
+                    SystemClock.elapsedRealtime() + FAIL_LOCKOUT_TIMEOUT_MS,
+                    mLockoutResetIntent.apply(userId));
         });
     }
-
-    private PendingIntent getLockoutResetIntentForUser(int userId) {
-        return PendingIntent.getBroadcast(mContext, userId,
-                new Intent(ACTION_LOCKOUT_RESET).putExtra(KEY_LOCKOUT_RESET_USER, userId),
-                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
-    }
 }
diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java
index b5846b5..a40dd79 100644
--- a/services/core/java/com/android/server/compat/CompatChange.java
+++ b/services/core/java/com/android/server/compat/CompatChange.java
@@ -252,9 +252,6 @@
             // If the change is gated by a platform version newer than the one currently installed
             // on the device, disregard the app's target sdk version.
             int compareSdk = Math.min(app.targetSdkVersion, buildClassifier.platformTargetSdk());
-            if (compareSdk != app.targetSdkVersion) {
-                compareSdk = app.targetSdkVersion;
-            }
             return compareSdk >= getEnableSinceTargetSdk();
         }
         return true;
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index c517058..b7ece2ea 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -1299,7 +1299,7 @@
             }
 
             try {
-                mNms.denyProtect(mOwnerUID);
+                mNetd.networkSetProtectDeny(mOwnerUID);
             } catch (Exception e) {
                 Log.wtf(TAG, "Failed to disallow UID " + mOwnerUID + " to call protect() " + e);
             }
@@ -1309,7 +1309,7 @@
             mOwnerUID = getAppUid(mContext, newPackage, mUserId);
             mIsPackageTargetingAtLeastQ = doesPackageTargetAtLeastQ(newPackage);
             try {
-                mNms.allowProtect(mOwnerUID);
+                mNetd.networkSetProtectAllow(mOwnerUID);
             } catch (Exception e) {
                 Log.wtf(TAG, "Failed to allow UID " + mOwnerUID + " to call protect() " + e);
             }
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index b542893..2314bb7 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityTaskManager;
 import android.app.ActivityTaskManager.RootTaskInfo;
@@ -40,6 +42,7 @@
 import android.util.EventLog;
 import android.util.MathUtils;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.TimeUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -49,6 +52,8 @@
 import com.android.server.display.brightness.BrightnessEvent;
 
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * Manages the associated display brightness when in auto-brightness mode. This is also
@@ -64,6 +69,16 @@
     public static final int AUTO_BRIGHTNESS_DISABLED = 2;
     public static final int AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE = 3;
 
+    @IntDef(prefix = { "AUTO_BRIGHTNESS_MODE_" }, value = {
+            AUTO_BRIGHTNESS_MODE_DEFAULT,
+            AUTO_BRIGHTNESS_MODE_IDLE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AutomaticBrightnessMode{}
+
+    public static final int AUTO_BRIGHTNESS_MODE_DEFAULT = 0;
+    public static final int AUTO_BRIGHTNESS_MODE_IDLE = 1;
+
     // How long the current sensor reading is assumed to be valid beyond the current time.
     // This provides a bit of prediction, as well as ensures that the weight for the last sample is
     // non-zero, which in turn ensures that the total weight is non-zero.
@@ -91,10 +106,11 @@
     private final Sensor mLightSensor;
 
     // The mapper to translate ambient lux to screen brightness in the range [0, 1.0].
-    @Nullable
+    @NonNull
     private BrightnessMappingStrategy mCurrentBrightnessMapper;
-    private final BrightnessMappingStrategy mInteractiveModeBrightnessMapper;
-    private final BrightnessMappingStrategy mIdleModeBrightnessMapper;
+
+    // A map of Brightness Mapping Strategies indexed by AutomaticBrightnessMode
+    private final SparseArray<BrightnessMappingStrategy> mBrightnessMappingStrategyMap;
 
     // The minimum and maximum screen brightnesses.
     private final float mScreenBrightnessRangeMinimum;
@@ -251,7 +267,7 @@
 
     AutomaticBrightnessController(Callbacks callbacks, Looper looper,
             SensorManager sensorManager, Sensor lightSensor,
-            BrightnessMappingStrategy interactiveModeBrightnessMapper,
+            SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap,
             int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
             float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
             long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
@@ -261,26 +277,25 @@
             HysteresisLevels ambientBrightnessThresholdsIdle,
             HysteresisLevels screenBrightnessThresholdsIdle, Context context,
             BrightnessRangeController brightnessModeController,
-            BrightnessThrottler brightnessThrottler,
-            BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
-            int ambientLightHorizonLong, float userLux, float userBrightness) {
+            BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort,
+            int ambientLightHorizonLong, float userLux, float userNits) {
         this(new Injector(), callbacks, looper, sensorManager, lightSensor,
-                interactiveModeBrightnessMapper,
-                lightSensorWarmUpTime, brightnessMin, brightnessMax, dozeScaleFactor,
-                lightSensorRate, initialLightSensorRate, brighteningLightDebounceConfig,
-                darkeningLightDebounceConfig, brighteningLightDebounceConfigIdle,
-                darkeningLightDebounceConfigIdle, resetAmbientLuxAfterWarmUpConfig,
-                ambientBrightnessThresholds, screenBrightnessThresholds,
-                ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, context,
-                brightnessModeController, brightnessThrottler, idleModeBrightnessMapper,
-                ambientLightHorizonShort, ambientLightHorizonLong, userLux, userBrightness
+                brightnessMappingStrategyMap, lightSensorWarmUpTime, brightnessMin, brightnessMax,
+                dozeScaleFactor, lightSensorRate, initialLightSensorRate,
+                brighteningLightDebounceConfig, darkeningLightDebounceConfig,
+                brighteningLightDebounceConfigIdle, darkeningLightDebounceConfigIdle,
+                resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds,
+                screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
+                screenBrightnessThresholdsIdle, context, brightnessModeController,
+                brightnessThrottler, ambientLightHorizonShort, ambientLightHorizonLong, userLux,
+                userNits
         );
     }
 
     @VisibleForTesting
     AutomaticBrightnessController(Injector injector, Callbacks callbacks, Looper looper,
             SensorManager sensorManager, Sensor lightSensor,
-            BrightnessMappingStrategy interactiveModeBrightnessMapper,
+            SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap,
             int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
             float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
             long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
@@ -290,15 +305,14 @@
             HysteresisLevels ambientBrightnessThresholdsIdle,
             HysteresisLevels screenBrightnessThresholdsIdle, Context context,
             BrightnessRangeController brightnessModeController,
-            BrightnessThrottler brightnessThrottler,
-            BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
-            int ambientLightHorizonLong, float userLux, float userBrightness) {
+            BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort,
+            int ambientLightHorizonLong, float userLux, float userNits) {
         mInjector = injector;
         mClock = injector.createClock();
         mContext = context;
         mCallbacks = callbacks;
         mSensorManager = sensorManager;
-        mCurrentBrightnessMapper = interactiveModeBrightnessMapper;
+        mCurrentBrightnessMapper = brightnessMappingStrategyMap.get(AUTO_BRIGHTNESS_MODE_DEFAULT);
         mScreenBrightnessRangeMinimum = brightnessMin;
         mScreenBrightnessRangeMaximum = brightnessMax;
         mLightSensorWarmUpTimeConfig = lightSensorWarmUpTime;
@@ -337,13 +351,12 @@
         mPendingForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
         mBrightnessRangeController = brightnessModeController;
         mBrightnessThrottler = brightnessThrottler;
-        mInteractiveModeBrightnessMapper = interactiveModeBrightnessMapper;
-        mIdleModeBrightnessMapper = idleModeBrightnessMapper;
-        // Initialize to active (normal) screen brightness mode
-        switchToInteractiveScreenBrightnessMode();
+        mBrightnessMappingStrategyMap = brightnessMappingStrategyMap;
 
         // Use the given short-term model
-        setScreenBrightnessByUser(userLux, userBrightness);
+        if (userNits != BrightnessMappingStrategy.INVALID_NITS) {
+            setScreenBrightnessByUser(userLux, getBrightnessFromNits(userNits));
+        }
     }
 
     /**
@@ -358,11 +371,8 @@
         if (mLoggingEnabled == loggingEnabled) {
             return false;
         }
-        if (mInteractiveModeBrightnessMapper != null) {
-            mInteractiveModeBrightnessMapper.setLoggingEnabled(loggingEnabled);
-        }
-        if (mIdleModeBrightnessMapper != null) {
-            mIdleModeBrightnessMapper.setLoggingEnabled(loggingEnabled);
+        for (int i = 0; i < mBrightnessMappingStrategyMap.size(); i++) {
+            mBrightnessMappingStrategyMap.valueAt(i).setLoggingEnabled(loggingEnabled);
         }
         mLoggingEnabled = loggingEnabled;
         return true;
@@ -389,8 +399,7 @@
                     | (!mAmbientLuxValid ? BrightnessEvent.FLAG_INVALID_LUX : 0)
                     | (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE
                         ? BrightnessEvent.FLAG_DOZE_SCALE : 0)
-                    | (mCurrentBrightnessMapper.isForIdleMode()
-                        ? BrightnessEvent.FLAG_IDLE_CURVE : 0));
+                    | (isInIdleMode() ? BrightnessEvent.FLAG_IDLE_CURVE : 0));
         }
 
         if (!mAmbientLuxValid) {
@@ -464,15 +473,13 @@
 
     // Used internally to establish whether we have deviated from the default config.
     public boolean isDefaultConfig() {
-        if (isInIdleMode()) {
-            return false;
-        }
-        return mInteractiveModeBrightnessMapper.isDefaultConfig();
+        return mCurrentBrightnessMapper.getMode() == AUTO_BRIGHTNESS_MODE_DEFAULT
+                && mCurrentBrightnessMapper.isDefaultConfig();
     }
 
     // Called from APIs to get the configuration.
     public BrightnessConfiguration getDefaultConfig() {
-        return mInteractiveModeBrightnessMapper.getDefaultConfig();
+        return mBrightnessMappingStrategyMap.get(AUTO_BRIGHTNESS_MODE_DEFAULT).getDefaultConfig();
     }
 
     /**
@@ -527,8 +534,7 @@
     }
 
     private boolean setScreenBrightnessByUser(float lux, float brightness) {
-        if (lux == BrightnessMappingStrategy.NO_USER_LUX
-                || brightness == BrightnessMappingStrategy.NO_USER_BRIGHTNESS) {
+        if (lux == BrightnessMappingStrategy.INVALID_LUX || Float.isNaN(brightness)) {
             return false;
         }
         mCurrentBrightnessMapper.addUserDataPoint(lux, brightness);
@@ -543,7 +549,8 @@
 
     public boolean setBrightnessConfiguration(BrightnessConfiguration configuration,
             boolean shouldResetShortTermModel) {
-        if (mInteractiveModeBrightnessMapper.setBrightnessConfiguration(configuration)) {
+        if (mBrightnessMappingStrategyMap.get(AUTO_BRIGHTNESS_MODE_DEFAULT)
+                .setBrightnessConfiguration(configuration)) {
             if (!isInIdleMode() && shouldResetShortTermModel) {
                 resetShortTermModel();
             }
@@ -553,7 +560,7 @@
     }
 
     public boolean isInIdleMode() {
-        return mCurrentBrightnessMapper.isForIdleMode();
+        return mCurrentBrightnessMapper.getMode() == AUTO_BRIGHTNESS_MODE_IDLE;
     }
 
     public void dump(PrintWriter pw) {
@@ -595,12 +602,6 @@
         pw.println("  mAmbientLightRingBuffer=" + mAmbientLightRingBuffer);
         pw.println("  mScreenAutoBrightness=" + mScreenAutoBrightness);
         pw.println("  mDisplayPolicy=" + DisplayPowerRequest.policyToString(mDisplayPolicy));
-        pw.println("  mShortTermModelTimeout(active)="
-                + mInteractiveModeBrightnessMapper.getShortTermModelTimeout());
-        if (mIdleModeBrightnessMapper != null) {
-            pw.println("  mShortTermModelTimeout(idle)="
-                    + mIdleModeBrightnessMapper.getShortTermModelTimeout());
-        }
         pw.println("  mShortTermModel=");
         mShortTermModel.dump(pw);
         pw.println("  mPausedShortTermModel=");
@@ -615,15 +616,14 @@
         pw.println("  mPendingForegroundAppPackageName=" + mPendingForegroundAppPackageName);
         pw.println("  mForegroundAppCategory=" + mForegroundAppCategory);
         pw.println("  mPendingForegroundAppCategory=" + mPendingForegroundAppCategory);
-        pw.println("  Idle mode active=" + mCurrentBrightnessMapper.isForIdleMode());
+        pw.println("  Current mode=" + mCurrentBrightnessMapper.getMode());
 
         pw.println();
-        pw.println("  mInteractiveMapper=");
-        mInteractiveModeBrightnessMapper.dump(pw,
-                mBrightnessRangeController.getNormalBrightnessMax());
-        if (mIdleModeBrightnessMapper != null) {
-            pw.println("  mIdleMapper=");
-            mIdleModeBrightnessMapper.dump(pw, mBrightnessRangeController.getNormalBrightnessMax());
+        for (int i = 0; i < mBrightnessMappingStrategyMap.size(); i++) {
+            pw.println("  Mapper for mode " + modeToString(mBrightnessMappingStrategyMap.keyAt(i))
+                    + "=");
+            mBrightnessMappingStrategyMap.valueAt(i).dump(pw,
+                    mBrightnessRangeController.getNormalBrightnessMax());
         }
 
         pw.println();
@@ -1117,84 +1117,71 @@
         updateAutoBrightness(true /* sendUpdate */, false /* isManuallySet */);
     }
 
-    void switchToIdleMode() {
-        if (mIdleModeBrightnessMapper == null) {
-            return;
-        }
-        if (mCurrentBrightnessMapper.isForIdleMode()) {
-            return;
-        }
-        Slog.i(TAG, "Switching to Idle Screen Brightness Mode");
+    private void switchModeAndShortTermModels(@AutomaticBrightnessMode int mode) {
         // Stash short term model
         ShortTermModel tempShortTermModel = new ShortTermModel();
         tempShortTermModel.set(mCurrentBrightnessMapper.getUserLux(),
                 mCurrentBrightnessMapper.getUserBrightness(), /* valid= */ true);
-
-        // Send delayed timeout
-        mHandler.sendEmptyMessageAtTime(MSG_INVALIDATE_PAUSED_SHORT_TERM_MODEL,
-                mClock.uptimeMillis()
-                        + mCurrentBrightnessMapper.getShortTermModelTimeout());
-
-        Slog.i(TAG, "mPreviousShortTermModel" + mPausedShortTermModel);
-        // new brightness mapper
-        mCurrentBrightnessMapper = mIdleModeBrightnessMapper;
-
-        // if previous stm has been invalidated, and lux has drastically changed, just use
-        // the new, reset stm.
-        // if previous stm is still valid then revalidate it
-        if (mPausedShortTermModel != null && !mPausedShortTermModel.maybeReset(mAmbientLux)) {
-            setScreenBrightnessByUser(mPausedShortTermModel.mAnchor,
-                    mPausedShortTermModel.mBrightness);
-        }
-        mPausedShortTermModel.copyFrom(tempShortTermModel);
-
-        update();
-    }
-
-    void switchToInteractiveScreenBrightnessMode() {
-        if (!mCurrentBrightnessMapper.isForIdleMode()) {
-            return;
-        }
-        Slog.i(TAG, "Switching to Interactive Screen Brightness Mode");
-        ShortTermModel tempShortTermModel = new ShortTermModel();
-        tempShortTermModel.set(mCurrentBrightnessMapper.getUserLux(),
-                mCurrentBrightnessMapper.getUserBrightness(), /* valid= */ true);
         mHandler.removeMessages(MSG_INVALIDATE_PAUSED_SHORT_TERM_MODEL);
         // Send delayed timeout
         mHandler.sendEmptyMessageAtTime(MSG_INVALIDATE_PAUSED_SHORT_TERM_MODEL,
                 mClock.uptimeMillis()
                         + mCurrentBrightnessMapper.getShortTermModelTimeout());
-        Slog.i(TAG, "mPreviousShortTermModel" + mPausedShortTermModel.toString());
 
-        // restore interactive mapper.
-        mCurrentBrightnessMapper = mInteractiveModeBrightnessMapper;
+        Slog.i(TAG, "mPreviousShortTermModel: " + mPausedShortTermModel);
+        // new brightness mapper
+        mCurrentBrightnessMapper = mBrightnessMappingStrategyMap.get(mode);
 
         // if previous stm has been invalidated, and lux has drastically changed, just use
         // the new, reset stm.
         // if previous stm is still valid then revalidate it
-        if (!mPausedShortTermModel.maybeReset(mAmbientLux)) {
-            setScreenBrightnessByUser(mPausedShortTermModel.mAnchor,
-                    mPausedShortTermModel.mBrightness);
+        if (mPausedShortTermModel != null) {
+            if (!mPausedShortTermModel.maybeReset(mAmbientLux)) {
+                setScreenBrightnessByUser(mPausedShortTermModel.mAnchor,
+                        mPausedShortTermModel.mBrightness);
+            }
+            mPausedShortTermModel.copyFrom(tempShortTermModel);
         }
-        mPausedShortTermModel.copyFrom(tempShortTermModel);
 
         update();
     }
 
+    void switchMode(@AutomaticBrightnessMode int mode) {
+        if (!mBrightnessMappingStrategyMap.contains(mode)) {
+            return;
+        }
+        if (mCurrentBrightnessMapper.getMode() == mode) {
+            return;
+        }
+        Slog.i(TAG, "Switching to mode " + mode);
+        if (mode == AUTO_BRIGHTNESS_MODE_IDLE
+                || mCurrentBrightnessMapper.getMode() == AUTO_BRIGHTNESS_MODE_IDLE) {
+            switchModeAndShortTermModels(mode);
+        } else {
+            resetShortTermModel();
+            mCurrentBrightnessMapper = mBrightnessMappingStrategyMap.get(mode);
+        }
+    }
+
+    float getUserLux() {
+        return mCurrentBrightnessMapper.getUserLux();
+    }
+
+    float getUserNits() {
+        return convertToNits(mCurrentBrightnessMapper.getUserBrightness());
+    }
+
     /**
      * Convert a brightness float scale value to a nit value. Adjustments, such as RBC, are not
      * applied. This is used when storing the brightness in nits for the default display and when
      * passing the brightness value to follower displays.
      *
      * @param brightness The float scale value
-     * @return The nit value or -1f if no conversion is possible.
+     * @return The nit value or {@link BrightnessMappingStrategy.INVALID_NITS} if no conversion is
+     * possible.
      */
     public float convertToNits(float brightness) {
-        if (mCurrentBrightnessMapper != null) {
-            return mCurrentBrightnessMapper.convertToNits(brightness);
-        } else {
-            return -1.0f;
-        }
+        return mCurrentBrightnessMapper.convertToNits(brightness);
     }
 
     /**
@@ -1203,14 +1190,11 @@
      * {@link com.android.server.display.BrightnessTracker}.
      *
      * @param brightness The float scale value
-     * @return The nit value or -1f if no conversion is possible.
+     * @return The nit value or {@link BrightnessMappingStrategy.INVALID_NITS} if no conversion is
+     * possible.
      */
     public float convertToAdjustedNits(float brightness) {
-        if (mCurrentBrightnessMapper != null) {
-            return mCurrentBrightnessMapper.convertToAdjustedNits(brightness);
-        } else {
-            return -1.0f;
-        }
+        return mCurrentBrightnessMapper.convertToAdjustedNits(brightness);
     }
 
     /**
@@ -1221,12 +1205,8 @@
      * @return The float scale value or {@link PowerManager.BRIGHTNESS_INVALID_FLOAT} if no
      * conversion is possible.
      */
-    public float convertToFloatScale(float nits) {
-        if (mCurrentBrightnessMapper != null) {
-            return mCurrentBrightnessMapper.convertToFloatScale(nits);
-        } else {
-            return PowerManager.BRIGHTNESS_INVALID_FLOAT;
-        }
+    public float getBrightnessFromNits(float nits) {
+        return mCurrentBrightnessMapper.getBrightnessFromNits(nits);
     }
 
     public void recalculateSplines(boolean applyAdjustment, float[] adjustment) {
@@ -1244,19 +1224,27 @@
         }
     }
 
+    private String modeToString(@AutomaticBrightnessMode int mode) {
+        return switch (mode) {
+            case AUTO_BRIGHTNESS_MODE_DEFAULT -> "default";
+            case AUTO_BRIGHTNESS_MODE_IDLE -> "idle";
+            default -> Integer.toString(mode);
+        };
+    }
+
     private class ShortTermModel {
         // When the short term model is invalidated, we don't necessarily reset it (i.e. clear the
         // user's adjustment) immediately, but wait for a drastic enough change in the ambient
         // light.
         // The anchor determines what were the light levels when the user has set their preference,
         // and we use a relative threshold to determine when to revert to the OEM curve.
-        private float mAnchor = BrightnessMappingStrategy.NO_USER_LUX;
-        private float mBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
+        private float mAnchor = BrightnessMappingStrategy.INVALID_LUX;
+        private float mBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
         private boolean mIsValid = false;
 
         private void reset() {
-            mAnchor = BrightnessMappingStrategy.NO_USER_LUX;
-            mBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
+            mAnchor = BrightnessMappingStrategy.INVALID_LUX;
+            mBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
             mIsValid = false;
         }
 
@@ -1279,10 +1267,8 @@
         private boolean maybeReset(float currentLux) {
             // If the short term model was invalidated and the change is drastic enough, reset it.
             // Otherwise, we revalidate it.
-            if (!mIsValid && mAnchor != -1) {
-                if (mCurrentBrightnessMapper != null
-                        && mCurrentBrightnessMapper.shouldResetShortTermModel(
-                        currentLux, mAnchor)) {
+            if (!mIsValid && mAnchor != BrightnessMappingStrategy.INVALID_LUX) {
+                if (mCurrentBrightnessMapper.shouldResetShortTermModel(currentLux, mAnchor)) {
                     resetShortTermModel();
                 } else {
                     mIsValid = true;
@@ -1304,7 +1290,7 @@
         }
 
         public String toString() {
-            return " mAnchor: " + mAnchor
+            return "mAnchor: " + mAnchor
                     + "\n mBrightness: " + mBrightness
                     + "\n mIsValid: " + mIsValid;
         }
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index d848f4b..f2ffd4d 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -18,6 +18,9 @@
 
 import static android.text.TextUtils.formatSimple;
 
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
+
 import android.annotation.Nullable;
 import android.content.pm.ApplicationInfo;
 import android.content.res.Resources;
@@ -32,7 +35,6 @@
 import android.util.Spline;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.display.BrightnessUtils;
 import com.android.internal.util.Preconditions;
 import com.android.server.display.utils.Plog;
@@ -57,8 +59,8 @@
 public abstract class BrightnessMappingStrategy {
     private static final String TAG = "BrightnessMappingStrategy";
 
-    public static final float NO_USER_LUX = -1;
-    public static final float NO_USER_BRIGHTNESS = -1;
+    public static final float INVALID_LUX = -1;
+    public static final float INVALID_NITS = -1;
 
     private static final float LUX_GRAD_SMOOTHING = 0.25f;
     private static final float MAX_GRAD = 1.0f;
@@ -74,74 +76,36 @@
     private static final Plog PLOG = Plog.createSystemPlog(TAG);
 
     /**
-     * Creates a BrightnessMappingStrategy for active (normal) mode.
-     * @param resources
-     * @param displayDeviceConfig
-     * @param displayWhiteBalanceController
-     * @return the BrightnessMappingStrategy
-     */
-    @Nullable
-    public static BrightnessMappingStrategy create(Resources resources,
-            DisplayDeviceConfig displayDeviceConfig,
-            DisplayWhiteBalanceController displayWhiteBalanceController) {
-        return create(resources, displayDeviceConfig, /* isForIdleMode= */ false,
-                displayWhiteBalanceController);
-    }
-
-    /**
-     * Creates a BrightnessMappingStrategy for idle screen brightness mode.
-     * @param resources
-     * @param displayDeviceConfig
-     * @param displayWhiteBalanceController
-     * @return the BrightnessMappingStrategy
-     */
-    @Nullable
-    public static BrightnessMappingStrategy createForIdleMode(Resources resources,
-            DisplayDeviceConfig displayDeviceConfig, DisplayWhiteBalanceController
-            displayWhiteBalanceController) {
-        return create(resources, displayDeviceConfig, /* isForIdleMode= */ true,
-                displayWhiteBalanceController);
-    }
-
-    /**
-     * Creates a BrightnessMapping strategy for either active or idle screen brightness mode.
-     * We do not create a simple mapping strategy for idle mode.
+     * Creates a BrightnessMapping strategy. We do not create a simple mapping strategy for idle
+     * mode.
      *
      * @param resources
      * @param displayDeviceConfig
-     * @param isForIdleMode determines whether the configurations loaded are for idle screen
-     *                      brightness mode or active screen brightness mode.
+     * @param mode The auto-brightness mode. Different modes use different brightness curves
      * @param displayWhiteBalanceController
      * @return the BrightnessMappingStrategy
      */
     @Nullable
-    private static BrightnessMappingStrategy create(Resources resources,
-            DisplayDeviceConfig displayDeviceConfig, boolean isForIdleMode,
+    static BrightnessMappingStrategy create(Resources resources,
+            DisplayDeviceConfig displayDeviceConfig,
+            @AutomaticBrightnessController.AutomaticBrightnessMode int mode,
             DisplayWhiteBalanceController displayWhiteBalanceController) {
 
         // Display independent, mode dependent values
-        float[] brightnessLevelsNits;
+        float[] brightnessLevelsNits = null;
         float[] brightnessLevels = null;
-        float[] luxLevels;
-        if (isForIdleMode) {
-            brightnessLevelsNits = getFloatArray(resources.obtainTypedArray(
-                    com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle));
-            luxLevels = getLuxLevels(resources.getIntArray(
-                    com.android.internal.R.array.config_autoBrightnessLevelsIdle));
-        } else {
-            brightnessLevelsNits = displayDeviceConfig.getAutoBrightnessBrighteningLevelsNits();
-            luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux();
-
-            brightnessLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevels();
-            if (brightnessLevels == null || brightnessLevels.length == 0) {
-                // Load the old configuration in the range [0, 255]. The values need to be
-                // normalized to the range [0, 1].
-                int[] brightnessLevelsInt = resources.getIntArray(
-                        com.android.internal.R.array.config_autoBrightnessLcdBacklightValues);
-                brightnessLevels = new float[brightnessLevelsInt.length];
-                for (int i = 0; i < brightnessLevels.length; i++) {
-                    brightnessLevels[i] = normalizeAbsoluteBrightness(brightnessLevelsInt[i]);
-                }
+        float[] luxLevels = null;
+        switch (mode) {
+            case AUTO_BRIGHTNESS_MODE_DEFAULT -> {
+                brightnessLevelsNits = displayDeviceConfig.getAutoBrightnessBrighteningLevelsNits();
+                luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux();
+                brightnessLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevels();
+            }
+            case AUTO_BRIGHTNESS_MODE_IDLE -> {
+                brightnessLevelsNits = getFloatArray(resources.obtainTypedArray(
+                        com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle));
+                luxLevels = getLuxLevels(resources.getIntArray(
+                        com.android.internal.R.array.config_autoBrightnessLevelsIdle));
             }
         }
 
@@ -158,17 +122,16 @@
 
         if (isValidMapping(nitsRange, brightnessRange)
                 && isValidMapping(luxLevels, brightnessLevelsNits)) {
-
             BrightnessConfiguration.Builder builder = new BrightnessConfiguration.Builder(
                     luxLevels, brightnessLevelsNits);
             builder.setShortTermModelTimeoutMillis(shortTermModelTimeout);
             builder.setShortTermModelLowerLuxMultiplier(SHORT_TERM_MODEL_THRESHOLD_RATIO);
             builder.setShortTermModelUpperLuxMultiplier(SHORT_TERM_MODEL_THRESHOLD_RATIO);
             return new PhysicalMappingStrategy(builder.build(), nitsRange, brightnessRange,
-                    autoBrightnessAdjustmentMaxGamma, isForIdleMode, displayWhiteBalanceController);
+                    autoBrightnessAdjustmentMaxGamma, mode, displayWhiteBalanceController);
         } else if (isValidMapping(luxLevels, brightnessLevels)) {
             return new SimpleMappingStrategy(luxLevels, brightnessLevels,
-                    autoBrightnessAdjustmentMaxGamma, shortTermModelTimeout);
+                    autoBrightnessAdjustmentMaxGamma, shortTermModelTimeout, mode);
         } else {
             return null;
         }
@@ -334,7 +297,7 @@
     /**
      * Converts the provided brightness value to nits if possible.
      *
-     * Returns -1.0f if there's no available mapping for the brightness to nits.
+     * Returns {@link INVALID_NITS} if there's no available mapping for the brightness to nits.
      */
     public abstract float convertToNits(float brightness);
 
@@ -342,7 +305,7 @@
      * Converts the provided brightness value to nits if possible. Adjustments, such as RBC are
      * applied.
      *
-     * Returns -1.0f if there's no available mapping for the brightness to nits.
+     * Returns {@link INVALID_NITS} if there's no available mapping for the brightness to nits.
      */
     public abstract float convertToAdjustedNits(float brightness);
 
@@ -352,7 +315,7 @@
      * Returns {@link PowerManager.BRIGHTNESS_INVALID_FLOAT} if there's no available mapping for
      * the nits to float scale.
      */
-    public abstract float convertToFloatScale(float nits);
+    public abstract float getBrightnessFromNits(float nits);
 
     /**
      * Adds a user interaction data point to the brightness mapping.
@@ -407,17 +370,18 @@
      */
     public abstract void dump(PrintWriter pw, float hbmTransition);
 
-    /**
-     * We can designate a mapping strategy to be used for idle screen brightness mode.
-     * @return whether this mapping strategy is to be used for idle screen brightness mode.
-     */
-    public abstract boolean isForIdleMode();
-
     abstract float getUserLux();
 
     abstract float getUserBrightness();
 
     /**
+     * @return The auto-brightness mode of this mapping strategy. Different modes use different
+     * brightness curves.
+     */
+    @AutomaticBrightnessController.AutomaticBrightnessMode
+    abstract int getMode();
+
+    /**
      * Check if the short term model should be reset given the anchor lux the last
      * brightness change was made at and the current ambient lux.
      */
@@ -450,11 +414,6 @@
         }
     }
 
-    // Normalize entire brightness range to 0 - 1.
-    protected static float normalizeAbsoluteBrightness(int brightness) {
-        return BrightnessSynchronizer.brightnessIntToFloat(brightness);
-    }
-
     private Pair<float[], float[]> insertControlPoint(
             float[] luxLevels, float[] brightnessLevels, float lux, float brightness) {
         final int idx = findInsertionPoint(luxLevels, lux);
@@ -596,7 +555,7 @@
         if (mLoggingEnabled) {
             PLOG.logCurve("gamma adjusted curve", newLux, newBrightness);
         }
-        if (userLux != -1) {
+        if (userLux != INVALID_LUX) {
             Pair<float[], float[]> curve = insertControlPoint(newLux, newBrightness, userLux,
                     userBrightness);
             newLux = curve.first;
@@ -624,6 +583,9 @@
         // Brightness control points normalized to [0, 1]
         private final float[] mBrightness;
 
+        @AutomaticBrightnessController.AutomaticBrightnessMode
+        private final int mMode;
+
         private Spline mSpline;
         private float mMaxGamma;
         private float mAutoBrightnessAdjustment;
@@ -632,7 +594,7 @@
         private long mShortTermModelTimeout;
 
         private SimpleMappingStrategy(float[] lux, float[] brightness, float maxGamma,
-                long timeout) {
+                long timeout, @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
             Preconditions.checkArgument(lux.length != 0 && brightness.length != 0,
                     "Lux and brightness arrays must not be empty!");
             Preconditions.checkArgument(lux.length == brightness.length,
@@ -651,13 +613,14 @@
 
             mMaxGamma = maxGamma;
             mAutoBrightnessAdjustment = 0;
-            mUserLux = NO_USER_LUX;
-            mUserBrightness = NO_USER_BRIGHTNESS;
+            mUserLux = INVALID_LUX;
+            mUserBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
             if (mLoggingEnabled) {
                 PLOG.start("simple mapping strategy");
             }
             computeSpline();
             mShortTermModelTimeout = timeout;
+            mMode = mode;
         }
 
         @Override
@@ -704,16 +667,16 @@
 
         @Override
         public float convertToNits(float brightness) {
-            return -1.0f;
+            return INVALID_NITS;
         }
 
         @Override
         public float convertToAdjustedNits(float brightness) {
-            return -1.0f;
+            return INVALID_NITS;
         }
 
         @Override
-        public float convertToFloatScale(float nits) {
+        public float getBrightnessFromNits(float nits) {
             return PowerManager.BRIGHTNESS_INVALID_FLOAT;
         }
 
@@ -741,22 +704,22 @@
 
         @Override
         public void clearUserDataPoints() {
-            if (mUserLux != -1) {
+            if (mUserLux != INVALID_LUX) {
                 if (mLoggingEnabled) {
                     Slog.d(TAG, "clearUserDataPoints: " + mAutoBrightnessAdjustment + " => 0");
                     PLOG.start("clear user data points")
                             .logPoint("user data point", mUserLux, mUserBrightness);
                 }
                 mAutoBrightnessAdjustment = 0;
-                mUserLux = -1;
-                mUserBrightness = -1;
+                mUserLux = INVALID_LUX;
+                mUserBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
                 computeSpline();
             }
         }
 
         @Override
         public boolean hasUserDataPoints() {
-            return mUserLux != -1;
+            return mUserLux != INVALID_LUX;
         }
 
         @Override
@@ -782,11 +745,12 @@
             pw.println("  mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment);
             pw.println("  mUserLux=" + mUserLux);
             pw.println("  mUserBrightness=" + mUserBrightness);
+            pw.println("  mShortTermModelTimeout=" + mShortTermModelTimeout);
         }
 
         @Override
-        public boolean isForIdleMode() {
-            return false;
+        int getMode() {
+            return mMode;
         }
 
         @Override
@@ -854,9 +818,11 @@
         private float mAutoBrightnessAdjustment;
         private float mUserLux;
         private float mUserBrightness;
-        private final boolean mIsForIdleMode;
         private final DisplayWhiteBalanceController mDisplayWhiteBalanceController;
 
+        @AutomaticBrightnessController.AutomaticBrightnessMode
+        private final int mMode;
+
         // Previous short-term models and the times that they were computed stored for debugging
         // purposes
         private List<Spline> mPreviousBrightnessSplines = new ArrayList<>();
@@ -865,7 +831,8 @@
         private static final SimpleDateFormat FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
 
         public PhysicalMappingStrategy(BrightnessConfiguration config, float[] nits,
-                float[] brightness, float maxGamma, boolean isForIdleMode,
+                float[] brightness, float maxGamma,
+                @AutomaticBrightnessController.AutomaticBrightnessMode int mode,
                 DisplayWhiteBalanceController displayWhiteBalanceController) {
 
             Preconditions.checkArgument(nits.length != 0 && brightness.length != 0,
@@ -878,11 +845,11 @@
             Preconditions.checkArrayElementsInRange(brightness,
                     PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, "brightness");
 
-            mIsForIdleMode = isForIdleMode;
+            mMode = mode;
             mMaxGamma = maxGamma;
             mAutoBrightnessAdjustment = 0;
-            mUserLux = NO_USER_LUX;
-            mUserBrightness = NO_USER_BRIGHTNESS;
+            mUserLux = INVALID_LUX;
+            mUserBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
             mDisplayWhiteBalanceController = displayWhiteBalanceController;
 
             mNits = nits;
@@ -982,7 +949,7 @@
         }
 
         @Override
-        public float convertToFloatScale(float nits) {
+        public float getBrightnessFromNits(float nits) {
             return mNitsToBrightnessSpline.interpolate(nits);
         }
 
@@ -1024,15 +991,15 @@
                             .logPoint("user data point", mUserLux, mUserBrightness);
                 }
                 mAutoBrightnessAdjustment = 0;
-                mUserLux = -1;
-                mUserBrightness = -1;
+                mUserLux = INVALID_LUX;
+                mUserBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
                 computeSpline();
             }
         }
 
         @Override
         public boolean hasUserDataPoints() {
-            return mUserLux != -1;
+            return mUserLux != INVALID_LUX;
         }
 
         @Override
@@ -1072,6 +1039,7 @@
             pw.println("  mUserBrightness=" + mUserBrightness);
             pw.println("  mDefaultConfig=" + mDefaultConfig);
             pw.println("  mBrightnessRangeAdjustmentApplied=" + mBrightnessRangeAdjustmentApplied);
+            pw.println("  shortTermModelTimeout=" + getShortTermModelTimeout());
 
             pw.println("  Previous short-term models (oldest to newest): ");
             for (int i = 0; i < mPreviousBrightnessSplines.size(); i++) {
@@ -1086,8 +1054,8 @@
         }
 
         @Override
-        public boolean isForIdleMode() {
-            return mIsForIdleMode;
+        int getMode() {
+            return mMode;
         }
 
         @Override
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index d97127c..7d22a87 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -49,6 +49,7 @@
 import com.android.server.display.config.BrightnessThrottlingMap;
 import com.android.server.display.config.BrightnessThrottlingPoint;
 import com.android.server.display.config.Density;
+import com.android.server.display.config.DisplayBrightnessMappingConfig;
 import com.android.server.display.config.DisplayBrightnessPoint;
 import com.android.server.display.config.DisplayConfiguration;
 import com.android.server.display.config.DisplayQuirks;
@@ -57,7 +58,6 @@
 import com.android.server.display.config.HighBrightnessMode;
 import com.android.server.display.config.IntegerArray;
 import com.android.server.display.config.LuxThrottling;
-import com.android.server.display.config.LuxToBrightnessMapping;
 import com.android.server.display.config.NitsMap;
 import com.android.server.display.config.NonNegativeFloatToFloatPoint;
 import com.android.server.display.config.Point;
@@ -313,6 +313,21 @@
  *              1000
  *          </darkeningLightDebounceIdleMillis>
  *          <luxToBrightnessMapping>
+ *            <mode>default</mode>
+ *            <map>
+ *              <point>
+ *                <first>0</first>
+ *                <second>0.2</second>
+ *              </point>
+ *              <point>
+ *                <first>80</first>
+ *                <second>0.3</second>
+ *              </point>
+ *            </map>
+ *          </luxToBrightnessMapping>
+ *          <luxToBrightnessMapping>
+ *            <mode>doze</mode>
+ *            <setting>dim</setting>
  *            <map>
  *              <point>
  *                <first>0</first>
@@ -634,36 +649,8 @@
     // for the corresponding values above
     private float[] mBrightness;
 
-    /**
-     * Array of desired screen brightness in nits corresponding to the lux values
-     * in the mBrightnessLevelsLux array. The display brightness is defined as the
-     * measured brightness of an all-white image. The brightness values must be non-negative and
-     * non-decreasing. This must be overridden in platform specific overlays
-     */
-    private float[] mBrightnessLevelsNits;
-
-    /**
-     * Array of desired screen brightness corresponding to the lux values
-     * in the mBrightnessLevelsLux array. The brightness values must be non-negative and
-     * non-decreasing. They must be between {@link PowerManager.BRIGHTNESS_MIN} and
-     * {@link PowerManager.BRIGHTNESS_MAX}. This must be overridden in platform specific overlays
-     */
-    private float[] mBrightnessLevels;
-
-    /**
-     * Array of light sensor lux values to define our levels for auto-brightness support.
-     *
-     * The first lux value is always 0.
-     *
-     * The control points must be strictly increasing. Each control point corresponds to an entry
-     * in the brightness values arrays. For example, if lux == luxLevels[1] (second element
-     * of the levels array) then the brightness will be determined by brightnessLevels[1] (second
-     * element of the brightness values array).
-     *
-     * Spline interpolation is used to determine the auto-brightness values for lux levels between
-     * these control points.
-     */
-    private float[] mBrightnessLevelsLux;
+    @Nullable
+    private DisplayBrightnessMappingConfig mDisplayBrightnessMapping;
 
     private float mBacklightMinimum = Float.NaN;
     private float mBacklightMaximum = Float.NaN;
@@ -1604,24 +1591,57 @@
     }
 
     /**
-     * @return Auto brightness brightening ambient lux levels
+     * @return The default auto-brightness brightening ambient lux levels
      */
     public float[] getAutoBrightnessBrighteningLevelsLux() {
-        return mBrightnessLevelsLux;
+        if (mDisplayBrightnessMapping == null) {
+            return null;
+        }
+        return mDisplayBrightnessMapping.getLuxArray();
+    }
+
+    /**
+     * @param mode The auto-brightness mode
+     * @param setting The brightness setting
+     * @return Auto brightness brightening ambient lux levels for the specified mode and setting
+     */
+    public float[] getAutoBrightnessBrighteningLevelsLux(String mode, String setting) {
+        if (mDisplayBrightnessMapping == null) {
+            return null;
+        }
+        return mDisplayBrightnessMapping.getLuxArray(mode, setting);
     }
 
     /**
      * @return Auto brightness brightening nits levels
      */
     public float[] getAutoBrightnessBrighteningLevelsNits() {
-        return mBrightnessLevelsNits;
+        if (mDisplayBrightnessMapping == null) {
+            return null;
+        }
+        return mDisplayBrightnessMapping.getNitsArray();
     }
 
     /**
-     * @return Auto brightness brightening levels
+     * @return The default auto-brightness brightening levels
      */
     public float[] getAutoBrightnessBrighteningLevels() {
-        return mBrightnessLevels;
+        if (mDisplayBrightnessMapping == null) {
+            return null;
+        }
+        return mDisplayBrightnessMapping.getBrightnessArray();
+    }
+
+    /**
+     * @param mode The auto-brightness mode
+     * @param setting The brightness setting
+     * @return Auto brightness brightening backlight levels for the specified mode and setting
+     */
+    public float[] getAutoBrightnessBrighteningLevels(String mode, String setting) {
+        if (mDisplayBrightnessMapping == null) {
+            return null;
+        }
+        return mDisplayBrightnessMapping.getBrightnessArray(mode, setting);
     }
 
     /**
@@ -1875,9 +1895,7 @@
                 + mAutoBrightnessBrighteningLightDebounceIdle
                 + ", mAutoBrightnessDarkeningLightDebounceIdle= "
                 + mAutoBrightnessDarkeningLightDebounceIdle
-                + ", mBrightnessLevelsLux= " + Arrays.toString(mBrightnessLevelsLux)
-                + ", mBrightnessLevelsNits= " + Arrays.toString(mBrightnessLevelsNits)
-                + ", mBrightnessLevels= " + Arrays.toString(mBrightnessLevels)
+                + ", mDisplayBrightnessMapping= " + mDisplayBrightnessMapping
                 + ", mDdcAutoBrightnessAvailable= " + mDdcAutoBrightnessAvailable
                 + ", mAutoBrightnessAvailable= " + mAutoBrightnessAvailable
                 + "\n"
@@ -2568,7 +2586,8 @@
         // Idle must be called after interactive, since we fall back to it if needed.
         loadAutoBrightnessBrighteningLightDebounceIdle(autoBrightness);
         loadAutoBrightnessDarkeningLightDebounceIdle(autoBrightness);
-        loadAutoBrightnessDisplayBrightnessMapping(autoBrightness);
+        mDisplayBrightnessMapping = new DisplayBrightnessMappingConfig(mContext, mFlags,
+                autoBrightness, mBacklightToBrightnessSpline);
         loadEnableAutoBrightness(autoBrightness);
     }
 
@@ -2633,38 +2652,6 @@
         }
     }
 
-    /**
-     * Loads the auto-brightness display brightness mappings. Internally, this takes care of
-     * loading the value from the display config, and if not present, falls back to config.xml.
-     */
-    private void loadAutoBrightnessDisplayBrightnessMapping(AutoBrightness autoBrightnessConfig) {
-        if (mFlags.areAutoBrightnessModesEnabled() && autoBrightnessConfig != null
-                && autoBrightnessConfig.getLuxToBrightnessMapping() != null) {
-            LuxToBrightnessMapping mapping = autoBrightnessConfig.getLuxToBrightnessMapping();
-            final int size = mapping.getMap().getPoint().size();
-            mBrightnessLevels = new float[size];
-            mBrightnessLevelsLux = new float[size];
-            for (int i = 0; i < size; i++) {
-                float backlight = mapping.getMap().getPoint().get(i).getSecond().floatValue();
-                mBrightnessLevels[i] = mBacklightToBrightnessSpline.interpolate(backlight);
-                mBrightnessLevelsLux[i] = mapping.getMap().getPoint().get(i).getFirst()
-                        .floatValue();
-            }
-            if (size > 0 && mBrightnessLevelsLux[0] != 0) {
-                throw new IllegalArgumentException(
-                        "The first lux value in the display brightness mapping must be 0");
-            }
-        } else {
-            mBrightnessLevelsNits = getFloatArray(mContext.getResources()
-                    .obtainTypedArray(com.android.internal.R.array
-                            .config_autoBrightnessDisplayValuesNits), PowerManager
-                    .BRIGHTNESS_OFF_FLOAT);
-            mBrightnessLevelsLux = getLuxLevels(mContext.getResources()
-                    .getIntArray(com.android.internal.R.array
-                            .config_autoBrightnessLevels));
-        }
-    }
-
     private void loadAutoBrightnessAvailableFromConfigXml() {
         mAutoBrightnessAvailable = mContext.getResources().getBoolean(
                 R.bool.config_automatic_brightness_available);
@@ -2977,7 +2964,8 @@
     }
 
     private void loadAutoBrightnessConfigsFromConfigXml() {
-        loadAutoBrightnessDisplayBrightnessMapping(null /*AutoBrightnessConfig*/);
+        mDisplayBrightnessMapping = new DisplayBrightnessMappingConfig(mContext, mFlags,
+                /* autoBrightnessConfig= */ null, mBacklightToBrightnessSpline);
     }
 
     private void loadBrightnessChangeThresholdsFromXml() {
@@ -3347,7 +3335,12 @@
         return vals;
     }
 
-    private static float[] getLuxLevels(int[] lux) {
+    /**
+     * @param lux The lux array
+     * @return The lux array with 0 appended at the beginning - the first lux value should always
+     * be 0
+     */
+    public static float[] getLuxLevels(int[] lux) {
         // The first control point is implicit and always at 0 lux.
         float[] levels = new float[lux.length + 1];
         for (int i = 0; i < lux.length; i++) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 2ab15e6..e38d08f 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2880,7 +2880,9 @@
             final DisplayPowerControllerInterface displayPowerController =
                     mDisplayPowerControllers.get(displayId);
             if (displayPowerController != null) {
-                displayPowerController.setAutomaticScreenBrightnessMode(enabled);
+                displayPowerController.setAutomaticScreenBrightnessMode(enabled
+                        ? AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE
+                        : AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT);
             }
         }
     }
diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
index 1bd556b..4e341a9 100644
--- a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
+++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
@@ -56,6 +56,15 @@
         }
     }
 
+    @Override
+    public boolean blockScreenOn(Runnable unblocker) {
+        if (mDisplayOffloader == null) {
+            return false;
+        }
+        mDisplayOffloader.onBlockingScreenOn(unblocker);
+        return true;
+    }
+
     /**
      * Start the offload session. The method returns if the session is already active.
      * @return Whether the session was started successfully
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 2cca72e..2685efe 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -16,6 +16,9 @@
 
 package com.android.server.display;
 
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
+
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
 import android.annotation.NonNull;
@@ -53,7 +56,6 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.FloatProperty;
-import android.util.Log;
 import android.util.MathUtils;
 import android.util.MutableFloat;
 import android.util.MutableInt;
@@ -498,15 +500,6 @@
     private Sensor mLightSensor;
     private Sensor mScreenOffBrightnessSensor;
 
-    // The mappers between ambient lux, display backlight values, and display brightness.
-    // We will switch between the idle mapper and active mapper in AutomaticBrightnessController.
-    // Mapper used for active (normal) screen brightness mode
-    @Nullable
-    private BrightnessMappingStrategy mInteractiveModeBrightnessMapper;
-    // Mapper used for idle screen brightness mode
-    @Nullable
-    private BrightnessMappingStrategy mIdleModeBrightnessMapper;
-
     // The current brightness configuration.
     @Nullable
     private BrightnessConfiguration mBrightnessConfiguration;
@@ -609,7 +602,7 @@
             BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting,
             Runnable onBrightnessChangeRunnable, HighBrightnessModeMetadata hbmMetadata,
             boolean bootCompleted, DisplayManagerFlags flags) {
-
+        mFlags = flags;
         mInjector = injector != null ? injector : new Injector();
         mClock = mInjector.getClock();
         mLogicalDisplay = logicalDisplay;
@@ -767,8 +760,6 @@
         mPendingAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
 
         mBootCompleted = bootCompleted;
-
-        mFlags = flags;
     }
 
     private void applyReduceBrightColorsSplineAdjustment() {
@@ -780,13 +771,6 @@
         if (mAutomaticBrightnessController == null) {
             return;
         }
-        if ((!mAutomaticBrightnessController.isInIdleMode()
-                && mInteractiveModeBrightnessMapper == null)
-                || (mAutomaticBrightnessController.isInIdleMode()
-                && mIdleModeBrightnessMapper == null)) {
-            Log.w(mTag, "No brightness mapping available to recalculate splines for this mode");
-            return;
-        }
 
         float[] adjustedNits = new float[mNitsRange.length];
         for (int i = 0; i < mNitsRange.length; i++) {
@@ -848,10 +832,10 @@
     public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux,
             boolean slowChange) {
         mBrightnessRangeController.onAmbientLuxChange(ambientLux);
-        if (nits < 0) {
+        if (nits == BrightnessMappingStrategy.INVALID_NITS) {
             mBrightnessToFollow = leadDisplayBrightness;
         } else {
-            float brightness = convertToFloatScale(nits);
+            float brightness = getBrightnessFromNits(nits);
             if (isValidBrightnessValue(brightness)) {
                 mBrightnessToFollow = brightness;
             } else {
@@ -876,7 +860,7 @@
         synchronized (mLock) {
             mDisplayBrightnessFollowers.remove(follower.getDisplayId());
             mHandler.postAtTime(() -> follower.setBrightnessToFollow(
-                    PowerManager.BRIGHTNESS_INVALID_FLOAT, /* nits= */ -1,
+                    PowerManager.BRIGHTNESS_INVALID_FLOAT, BrightnessMappingStrategy.INVALID_NITS,
                     /* ambientLux= */ 0, /* slowChange= */ false), mClock.uptimeMillis());
         }
     }
@@ -886,7 +870,7 @@
         for (int i = 0; i < mDisplayBrightnessFollowers.size(); i++) {
             DisplayPowerControllerInterface follower = mDisplayBrightnessFollowers.valueAt(i);
             mHandler.postAtTime(() -> follower.setBrightnessToFollow(
-                    PowerManager.BRIGHTNESS_INVALID_FLOAT, /* nits= */ -1,
+                    PowerManager.BRIGHTNESS_INVALID_FLOAT, BrightnessMappingStrategy.INVALID_NITS,
                     /* ambientLux= */ 0, /* slowChange= */ false), mClock.uptimeMillis());
         }
         mDisplayBrightnessFollowers.clear();
@@ -1168,24 +1152,35 @@
             return;
         }
 
-        float userLux = BrightnessMappingStrategy.NO_USER_LUX;
-        float userNits = -1;
-        if (mInteractiveModeBrightnessMapper != null) {
-            userLux = mInteractiveModeBrightnessMapper.getUserLux();
-            float userBrightness = mInteractiveModeBrightnessMapper.getUserBrightness();
-            userNits = mInteractiveModeBrightnessMapper.convertToNits(userBrightness);
-        }
+        SparseArray<BrightnessMappingStrategy> brightnessMappers = new SparseArray<>();
+
+        BrightnessMappingStrategy defaultModeBrightnessMapper =
+                mInjector.getDefaultModeBrightnessMapper(resources, mDisplayDeviceConfig,
+                        mDisplayWhiteBalanceController);
+        brightnessMappers.append(AUTO_BRIGHTNESS_MODE_DEFAULT,
+                defaultModeBrightnessMapper);
 
         final boolean isIdleScreenBrightnessEnabled = resources.getBoolean(
                 R.bool.config_enableIdleScreenBrightnessMode);
-        mInteractiveModeBrightnessMapper = mInjector.getInteractiveModeBrightnessMapper(resources,
-                mDisplayDeviceConfig, mDisplayWhiteBalanceController);
         if (isIdleScreenBrightnessEnabled) {
-            mIdleModeBrightnessMapper = BrightnessMappingStrategy.createForIdleMode(resources,
-                    mDisplayDeviceConfig, mDisplayWhiteBalanceController);
+            BrightnessMappingStrategy idleModeBrightnessMapper =
+                    BrightnessMappingStrategy.create(resources, mDisplayDeviceConfig,
+                            AUTO_BRIGHTNESS_MODE_IDLE,
+                            mDisplayWhiteBalanceController);
+            if (idleModeBrightnessMapper != null) {
+                brightnessMappers.append(AUTO_BRIGHTNESS_MODE_IDLE,
+                        idleModeBrightnessMapper);
+            }
         }
 
-        if (mInteractiveModeBrightnessMapper != null) {
+        float userLux = BrightnessMappingStrategy.INVALID_LUX;
+        float userNits = BrightnessMappingStrategy.INVALID_NITS;
+        if (mAutomaticBrightnessController != null) {
+            userLux = mAutomaticBrightnessController.getUserLux();
+            userNits = mAutomaticBrightnessController.getUserNits();
+        }
+
+        if (defaultModeBrightnessMapper != null) {
             final float dozeScaleFactor = resources.getFraction(
                     com.android.internal.R.fraction.config_screenAutoBrightnessDozeScaleFactor,
                     1, 1);
@@ -1297,25 +1292,17 @@
             if (mAutomaticBrightnessController != null) {
                 mAutomaticBrightnessController.stop();
             }
-            float userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
-            if (userNits >= 0) {
-                userBrightness = mInteractiveModeBrightnessMapper.convertToFloatScale(userNits);
-                if (Float.isNaN(userBrightness)) {
-                    userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
-                }
-            }
             mAutomaticBrightnessController = mInjector.getAutomaticBrightnessController(
                     this, handler.getLooper(), mSensorManager, mLightSensor,
-                    mInteractiveModeBrightnessMapper, lightSensorWarmUpTimeConfig,
-                    PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, dozeScaleFactor,
-                    lightSensorRate, initialLightSensorRate, brighteningLightDebounce,
-                    darkeningLightDebounce, brighteningLightDebounceIdle,
-                    darkeningLightDebounceIdle, autoBrightnessResetAmbientLuxAfterWarmUp,
-                    ambientBrightnessThresholds, screenBrightnessThresholds,
-                    ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, mContext,
-                    mBrightnessRangeController, mBrightnessThrottler, mIdleModeBrightnessMapper,
-                    mDisplayDeviceConfig.getAmbientHorizonShort(),
-                    mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userBrightness);
+                    brightnessMappers, lightSensorWarmUpTimeConfig, PowerManager.BRIGHTNESS_MIN,
+                    PowerManager.BRIGHTNESS_MAX, dozeScaleFactor, lightSensorRate,
+                    initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce,
+                    brighteningLightDebounceIdle, darkeningLightDebounceIdle,
+                    autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds,
+                    screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
+                    screenBrightnessThresholdsIdle, mContext, mBrightnessRangeController,
+                    mBrightnessThrottler, mDisplayDeviceConfig.getAmbientHorizonShort(),
+                    mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userNits);
 
             mBrightnessEventRingBuffer =
                     new RingBuffer<>(BrightnessEvent.class, RINGBUFFER_MAX);
@@ -1334,7 +1321,7 @@
                                 mHandler,
                                 SystemClock::uptimeMillis,
                                 sensorValueToLux,
-                                mInteractiveModeBrightnessMapper);
+                                defaultModeBrightnessMapper);
             }
         } else {
             mUseSoftwareAutoBrightnessConfig = false;
@@ -1377,13 +1364,11 @@
     }
 
     @Override
-    public void setAutomaticScreenBrightnessMode(boolean isIdle) {
+    public void setAutomaticScreenBrightnessMode(
+            @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+        boolean isIdle = mode == AUTO_BRIGHTNESS_MODE_IDLE;
         if (mAutomaticBrightnessController != null) {
-            if (isIdle) {
-                mAutomaticBrightnessController.switchToIdleMode();
-            } else {
-                mAutomaticBrightnessController.switchToInteractiveScreenBrightnessMode();
-            }
+            mAutomaticBrightnessController.switchMode(mode);
             setAnimatorRampSpeeds(isIdle);
         }
 
@@ -2810,7 +2795,7 @@
             float brightnessNitsForDefaultDisplay =
                     mBrightnessSetting.getBrightnessNitsForDefaultDisplay();
             if (brightnessNitsForDefaultDisplay >= 0) {
-                float brightnessForDefaultDisplay = convertToFloatScale(
+                float brightnessForDefaultDisplay = getBrightnessFromNits(
                         brightnessNitsForDefaultDisplay);
                 if (isValidBrightnessValue(brightnessForDefaultDisplay)) {
                     mBrightnessSetting.setBrightness(brightnessForDefaultDisplay);
@@ -2944,23 +2929,23 @@
 
     private float convertToNits(float brightness) {
         if (mAutomaticBrightnessController == null) {
-            return -1f;
+            return BrightnessMappingStrategy.INVALID_NITS;
         }
         return mAutomaticBrightnessController.convertToNits(brightness);
     }
 
     private float convertToAdjustedNits(float brightness) {
         if (mAutomaticBrightnessController == null) {
-            return -1f;
+            return BrightnessMappingStrategy.INVALID_NITS;
         }
         return mAutomaticBrightnessController.convertToAdjustedNits(brightness);
     }
 
-    private float convertToFloatScale(float nits) {
+    private float getBrightnessFromNits(float nits) {
         if (mAutomaticBrightnessController == null) {
             return PowerManager.BRIGHTNESS_INVALID_FLOAT;
         }
-        return mAutomaticBrightnessController.convertToFloatScale(nits);
+        return mAutomaticBrightnessController.getBrightnessFromNits(nits);
     }
 
     @GuardedBy("mLock")
@@ -3635,7 +3620,7 @@
         AutomaticBrightnessController getAutomaticBrightnessController(
                 AutomaticBrightnessController.Callbacks callbacks, Looper looper,
                 SensorManager sensorManager, Sensor lightSensor,
-                BrightnessMappingStrategy interactiveModeBrightnessMapper,
+                SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap,
                 int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
                 float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
                 long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
@@ -3645,27 +3630,27 @@
                 HysteresisLevels screenBrightnessThresholds,
                 HysteresisLevels ambientBrightnessThresholdsIdle,
                 HysteresisLevels screenBrightnessThresholdsIdle, Context context,
-                BrightnessRangeController brightnessRangeController,
-                BrightnessThrottler brightnessThrottler,
-                BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
-                int ambientLightHorizonLong, float userLux, float userBrightness) {
+                BrightnessRangeController brightnessModeController,
+                BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort,
+                int ambientLightHorizonLong, float userLux, float userNits) {
             return new AutomaticBrightnessController(callbacks, looper, sensorManager, lightSensor,
-                    interactiveModeBrightnessMapper, lightSensorWarmUpTime, brightnessMin,
+                    brightnessMappingStrategyMap, lightSensorWarmUpTime, brightnessMin,
                     brightnessMax, dozeScaleFactor, lightSensorRate, initialLightSensorRate,
                     brighteningLightDebounceConfig, darkeningLightDebounceConfig,
                     brighteningLightDebounceConfigIdle, darkeningLightDebounceConfigIdle,
                     resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds,
                     screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
-                    screenBrightnessThresholdsIdle, context, brightnessRangeController,
-                    brightnessThrottler, idleModeBrightnessMapper, ambientLightHorizonShort,
-                    ambientLightHorizonLong, userLux, userBrightness);
+                    screenBrightnessThresholdsIdle, context, brightnessModeController,
+                    brightnessThrottler, ambientLightHorizonShort, ambientLightHorizonLong, userLux,
+                    userNits);
         }
 
-        BrightnessMappingStrategy getInteractiveModeBrightnessMapper(Resources resources,
+        BrightnessMappingStrategy getDefaultModeBrightnessMapper(Resources resources,
                 DisplayDeviceConfig displayDeviceConfig,
                 DisplayWhiteBalanceController displayWhiteBalanceController) {
             return BrightnessMappingStrategy.create(resources,
-                    displayDeviceConfig, displayWhiteBalanceController);
+                    displayDeviceConfig, AUTO_BRIGHTNESS_MODE_DEFAULT,
+                    displayWhiteBalanceController);
         }
 
         HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 810ac08..6d09cc9 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -16,6 +16,9 @@
 
 package com.android.server.display;
 
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
+
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
 import android.annotation.Nullable;
@@ -52,7 +55,6 @@
 import android.provider.Settings;
 import android.util.FloatProperty;
 import android.util.IndentingPrintWriter;
-import android.util.Log;
 import android.util.MathUtils;
 import android.util.MutableFloat;
 import android.util.MutableInt;
@@ -124,6 +126,8 @@
     // To enable these logs, run:
     // 'adb shell setprop persist.log.tag.DisplayPowerController2 DEBUG && adb reboot'
     private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
+    private static final String SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME =
+            "Screen on blocked by displayoffload";
 
     // If true, uses the color fade on animation.
     // We might want to turn this off if we cannot get a guarantee that the screen
@@ -153,6 +157,7 @@
     private static final int MSG_SET_DWBC_COLOR_OVERRIDE = 15;
     private static final int MSG_SET_DWBC_LOGGING_ENABLED = 16;
     private static final int MSG_SET_BRIGHTNESS_FROM_OFFLOAD = 17;
+    private static final int MSG_OFFLOADING_SCREEN_ON_UNBLOCKED = 18;
 
 
 
@@ -337,6 +342,7 @@
     // we are waiting for a callback to release it and unblock the screen.
     private ScreenOnUnblocker mPendingScreenOnUnblocker;
     private ScreenOffUnblocker mPendingScreenOffUnblocker;
+    private Runnable mPendingScreenOnUnblockerByDisplayOffload;
 
     // True if we were in the process of turning off the screen.
     // This allows us to recover more gracefully from situations where we abort
@@ -346,10 +352,15 @@
     // The elapsed real time when the screen on was blocked.
     private long mScreenOnBlockStartRealTime;
     private long mScreenOffBlockStartRealTime;
+    private long mScreenOnBlockByDisplayOffloadStartRealTime;
 
     // Screen state we reported to policy. Must be one of REPORTED_TO_POLICY_* fields.
     private int mReportedScreenStateToPolicy = REPORTED_TO_POLICY_UNREPORTED;
 
+    // Used to deduplicate the displayoffload blocking screen on logic. One block per turning on.
+    // This value is reset when screen on is reported or the blocking is cancelled.
+    private boolean mScreenTurningOnWasBlockedByDisplayOffload;
+
     // If the last recorded screen state was dozing or not.
     private boolean mDozing;
 
@@ -442,15 +453,6 @@
     private Sensor mLightSensor;
     private Sensor mScreenOffBrightnessSensor;
 
-    // The mappers between ambient lux, display backlight values, and display brightness.
-    // We will switch between the idle mapper and active mapper in AutomaticBrightnessController.
-    // Mapper used for active (normal) screen brightness mode
-    @Nullable
-    private BrightnessMappingStrategy mInteractiveModeBrightnessMapper;
-    // Mapper used for idle screen brightness mode
-    @Nullable
-    private BrightnessMappingStrategy mIdleModeBrightnessMapper;
-
     private boolean mIsRbcActive;
 
     // Animators.
@@ -479,7 +481,7 @@
     private boolean mBootCompleted;
     private final DisplayManagerFlags mFlags;
 
-    private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession;
+    private DisplayOffloadSession mDisplayOffloadSession;
 
     /**
      * Creates the display power controller.
@@ -490,7 +492,7 @@
             BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting,
             Runnable onBrightnessChangeRunnable, HighBrightnessModeMetadata hbmMetadata,
             boolean bootCompleted, DisplayManagerFlags flags) {
-
+        mFlags = flags;
         mInjector = injector != null ? injector : new Injector();
         mClock = mInjector.getClock();
         mLogicalDisplay = logicalDisplay;
@@ -636,7 +638,6 @@
                 R.bool.config_displayBrightnessBucketsInDoze);
 
         mBootCompleted = bootCompleted;
-        mFlags = flags;
     }
 
     private void applyReduceBrightColorsSplineAdjustment() {
@@ -648,13 +649,6 @@
         if (mAutomaticBrightnessController == null) {
             return;
         }
-        if ((!mAutomaticBrightnessController.isInIdleMode()
-                && mInteractiveModeBrightnessMapper == null)
-                || (mAutomaticBrightnessController.isInIdleMode()
-                && mIdleModeBrightnessMapper == null)) {
-            Log.w(mTag, "No brightness mapping available to recalculate splines for this mode");
-            return;
-        }
 
         float[] adjustedNits = new float[mNitsRange.length];
         for (int i = 0; i < mNitsRange.length; i++) {
@@ -787,6 +781,10 @@
 
     @Override
     public void setDisplayOffloadSession(DisplayOffloadSession session) {
+        if (session == mDisplayOffloadSession) {
+            return;
+        }
+        unblockScreenOnByDisplayOffload();
         mDisplayOffloadSession = session;
     }
 
@@ -987,24 +985,35 @@
             return;
         }
 
-        float userLux = BrightnessMappingStrategy.NO_USER_LUX;
-        float userNits = -1;
-        if (mInteractiveModeBrightnessMapper != null) {
-            userLux = mInteractiveModeBrightnessMapper.getUserLux();
-            float userBrightness = mInteractiveModeBrightnessMapper.getUserBrightness();
-            userNits = mInteractiveModeBrightnessMapper.convertToNits(userBrightness);
-        }
+        SparseArray<BrightnessMappingStrategy> brightnessMappers = new SparseArray<>();
+
+        BrightnessMappingStrategy defaultModeBrightnessMapper =
+                mInjector.getDefaultModeBrightnessMapper(resources, mDisplayDeviceConfig,
+                        mDisplayWhiteBalanceController);
+        brightnessMappers.append(AUTO_BRIGHTNESS_MODE_DEFAULT,
+                defaultModeBrightnessMapper);
 
         final boolean isIdleScreenBrightnessEnabled = resources.getBoolean(
                 R.bool.config_enableIdleScreenBrightnessMode);
-        mInteractiveModeBrightnessMapper = mInjector.getInteractiveModeBrightnessMapper(resources,
-                mDisplayDeviceConfig, mDisplayWhiteBalanceController);
         if (isIdleScreenBrightnessEnabled) {
-            mIdleModeBrightnessMapper = BrightnessMappingStrategy.createForIdleMode(resources,
-                    mDisplayDeviceConfig, mDisplayWhiteBalanceController);
+            BrightnessMappingStrategy idleModeBrightnessMapper =
+                    BrightnessMappingStrategy.create(resources, mDisplayDeviceConfig,
+                            AUTO_BRIGHTNESS_MODE_IDLE,
+                            mDisplayWhiteBalanceController);
+            if (idleModeBrightnessMapper != null) {
+                brightnessMappers.append(AUTO_BRIGHTNESS_MODE_IDLE,
+                        idleModeBrightnessMapper);
+            }
         }
 
-        if (mInteractiveModeBrightnessMapper != null) {
+        float userLux = BrightnessMappingStrategy.INVALID_LUX;
+        float userNits = BrightnessMappingStrategy.INVALID_NITS;
+        if (mAutomaticBrightnessController != null) {
+            userLux = mAutomaticBrightnessController.getUserLux();
+            userNits = mAutomaticBrightnessController.getUserNits();
+        }
+
+        if (defaultModeBrightnessMapper != null) {
             final float dozeScaleFactor = resources.getFraction(
                     R.fraction.config_screenAutoBrightnessDozeScaleFactor,
                     1, 1);
@@ -1116,25 +1125,17 @@
             if (mAutomaticBrightnessController != null) {
                 mAutomaticBrightnessController.stop();
             }
-            float userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
-            if (userNits >= 0) {
-                userBrightness = mInteractiveModeBrightnessMapper.convertToFloatScale(userNits);
-                if (Float.isNaN(userBrightness)) {
-                    userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
-                }
-            }
             mAutomaticBrightnessController = mInjector.getAutomaticBrightnessController(
                     this, handler.getLooper(), mSensorManager, mLightSensor,
-                    mInteractiveModeBrightnessMapper, lightSensorWarmUpTimeConfig,
-                    PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, dozeScaleFactor,
-                    lightSensorRate, initialLightSensorRate, brighteningLightDebounce,
-                    darkeningLightDebounce, brighteningLightDebounceIdle,
-                    darkeningLightDebounceIdle, autoBrightnessResetAmbientLuxAfterWarmUp,
-                    ambientBrightnessThresholds, screenBrightnessThresholds,
-                    ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, mContext,
-                    mBrightnessRangeController, mBrightnessThrottler, mIdleModeBrightnessMapper,
-                    mDisplayDeviceConfig.getAmbientHorizonShort(),
-                    mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userBrightness);
+                    brightnessMappers, lightSensorWarmUpTimeConfig, PowerManager.BRIGHTNESS_MIN,
+                    PowerManager.BRIGHTNESS_MAX, dozeScaleFactor, lightSensorRate,
+                    initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce,
+                    brighteningLightDebounceIdle, darkeningLightDebounceIdle,
+                    autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds,
+                    screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
+                    screenBrightnessThresholdsIdle, mContext, mBrightnessRangeController,
+                    mBrightnessThrottler, mDisplayDeviceConfig.getAmbientHorizonShort(),
+                    mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userNits);
             mDisplayBrightnessController.setAutomaticBrightnessController(
                     mAutomaticBrightnessController);
 
@@ -1157,7 +1158,7 @@
                                 mHandler,
                                 SystemClock::uptimeMillis,
                                 sensorValueToLux,
-                                mInteractiveModeBrightnessMapper);
+                                defaultModeBrightnessMapper);
             }
         } else {
             mUseSoftwareAutoBrightnessConfig = false;
@@ -1200,13 +1201,11 @@
     }
 
     @Override
-    public void setAutomaticScreenBrightnessMode(boolean isIdle) {
+    public void setAutomaticScreenBrightnessMode(
+            @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+        boolean isIdle = mode == AUTO_BRIGHTNESS_MODE_IDLE;
         if (mAutomaticBrightnessController != null) {
-            if (isIdle) {
-                mAutomaticBrightnessController.switchToIdleMode();
-            } else {
-                mAutomaticBrightnessController.switchToInteractiveScreenBrightnessMode();
-            }
+            mAutomaticBrightnessController.switchMode(mode);
             setAnimatorRampSpeeds(isIdle);
         }
         Message msg = mHandler.obtainMessage();
@@ -1749,6 +1748,7 @@
         // reporting the display is ready because we only need to ensure the screen is in the
         // right power state even as it continues to converge on the desired brightness.
         final boolean ready = mPendingScreenOnUnblocker == null
+                && mPendingScreenOnUnblockerByDisplayOffload == null
                 && (!mColorFadeEnabled || (!mColorFadeOnAnimator.isStarted()
                         && !mColorFadeOffAnimator.isStarted()))
                 && mPowerState.waitUntilClean(mCleanListener);
@@ -1997,15 +1997,69 @@
         }
     }
 
+    private void blockScreenOnByDisplayOffload(DisplayOffloadSession displayOffloadSession) {
+        if (mPendingScreenOnUnblockerByDisplayOffload != null || displayOffloadSession == null) {
+            return;
+        }
+        mScreenTurningOnWasBlockedByDisplayOffload = true;
+
+        Trace.asyncTraceBegin(
+                Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME, 0);
+        mScreenOnBlockByDisplayOffloadStartRealTime = SystemClock.elapsedRealtime();
+
+        mPendingScreenOnUnblockerByDisplayOffload =
+                () -> onDisplayOffloadUnblockScreenOn(displayOffloadSession);
+        if (!displayOffloadSession.blockScreenOn(mPendingScreenOnUnblockerByDisplayOffload)) {
+            mPendingScreenOnUnblockerByDisplayOffload = null;
+            long delay =
+                    SystemClock.elapsedRealtime() - mScreenOnBlockByDisplayOffloadStartRealTime;
+            Slog.w(mTag, "Tried blocking screen on for offloading but failed. So, end trace after "
+                    + delay + " ms.");
+            Trace.asyncTraceEnd(
+                    Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME, 0);
+            return;
+        }
+        Slog.i(mTag, "Blocking screen on for offloading.");
+    }
+
+    private void onDisplayOffloadUnblockScreenOn(DisplayOffloadSession displayOffloadSession) {
+        Message msg = mHandler.obtainMessage(MSG_OFFLOADING_SCREEN_ON_UNBLOCKED,
+                displayOffloadSession);
+        mHandler.sendMessage(msg);
+    }
+
+    private void unblockScreenOnByDisplayOffload() {
+        if (mPendingScreenOnUnblockerByDisplayOffload == null) {
+            return;
+        }
+        mPendingScreenOnUnblockerByDisplayOffload = null;
+        long delay = SystemClock.elapsedRealtime() - mScreenOnBlockByDisplayOffloadStartRealTime;
+        Slog.i(mTag, "Unblocked screen on for offloading after " + delay + " ms");
+        Trace.asyncTraceEnd(
+                Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME, 0);
+    }
+
     private boolean setScreenState(int state) {
         return setScreenState(state, false /*reportOnly*/);
     }
 
     private boolean setScreenState(int state, boolean reportOnly) {
         final boolean isOff = (state == Display.STATE_OFF);
+        final boolean isOn = (state == Display.STATE_ON);
+        final boolean changed = mPowerState.getScreenState() != state;
 
-        if (mPowerState.getScreenState() != state
-                || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED) {
+        // If the screen is turning on, give displayoffload a chance to do something before the
+        // screen actually turns on.
+        // TODO(b/316941732): add tests for this displayoffload screen-on blocker.
+        if (isOn && changed && !mScreenTurningOnWasBlockedByDisplayOffload) {
+            blockScreenOnByDisplayOffload(mDisplayOffloadSession);
+        } else if (!isOn && mScreenTurningOnWasBlockedByDisplayOffload) {
+            // No longer turning screen on, so unblock previous screen on blocking immediately.
+            unblockScreenOnByDisplayOffload();
+            mScreenTurningOnWasBlockedByDisplayOffload = false;
+        }
+
+        if (changed || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED) {
             // If we are trying to turn screen off, give policy a chance to do something before we
             // actually turn the screen off.
             if (isOff && !mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()) {
@@ -2021,8 +2075,9 @@
                 }
             }
 
-            if (!reportOnly && mPowerState.getScreenState() != state
-                    && readyToUpdateDisplayState()) {
+            if (!reportOnly && changed && readyToUpdateDisplayState()
+                    && mPendingScreenOffUnblocker == null
+                    && mPendingScreenOnUnblockerByDisplayOffload == null) {
                 Trace.traceCounter(Trace.TRACE_TAG_POWER, "ScreenState", state);
 
                 String propertyKey = "debug.tracing.screen_state";
@@ -2074,12 +2129,16 @@
         }
 
         // Return true if the screen isn't blocked.
-        return mPendingScreenOnUnblocker == null;
+        return mPendingScreenOnUnblocker == null
+                && mPendingScreenOnUnblockerByDisplayOffload == null;
     }
 
     private void setReportedScreenState(int state) {
         Trace.traceCounter(Trace.TRACE_TAG_POWER, "ReportedScreenStateToPolicy", state);
         mReportedScreenStateToPolicy = state;
+        if (state == REPORTED_TO_POLICY_SCREEN_ON) {
+            mScreenTurningOnWasBlockedByDisplayOffload = false;
+        }
     }
 
     private void loadAmbientLightSensor() {
@@ -2351,10 +2410,10 @@
     public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux,
             boolean slowChange) {
         mBrightnessRangeController.onAmbientLuxChange(ambientLux);
-        if (nits < 0) {
+        if (nits == BrightnessMappingStrategy.INVALID_NITS) {
             mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness, slowChange);
         } else {
-            float brightness = mDisplayBrightnessController.convertToFloatScale(nits);
+            float brightness = mDisplayBrightnessController.getBrightnessFromNits(nits);
             if (BrightnessUtils.isValidBrightnessValue(brightness)) {
                 mDisplayBrightnessController.setBrightnessToFollow(brightness, slowChange);
             } else {
@@ -2421,7 +2480,7 @@
         synchronized (mLock) {
             mDisplayBrightnessFollowers.remove(follower.getDisplayId());
             mHandler.postAtTime(() -> follower.setBrightnessToFollow(
-                    PowerManager.BRIGHTNESS_INVALID_FLOAT, /* nits= */ -1,
+                    PowerManager.BRIGHTNESS_INVALID_FLOAT, BrightnessMappingStrategy.INVALID_NITS,
                     /* ambientLux= */ 0, /* slowChange= */ false), mClock.uptimeMillis());
         }
     }
@@ -2431,7 +2490,7 @@
         for (int i = 0; i < mDisplayBrightnessFollowers.size(); i++) {
             DisplayPowerControllerInterface follower = mDisplayBrightnessFollowers.valueAt(i);
             mHandler.postAtTime(() -> follower.setBrightnessToFollow(
-                    PowerManager.BRIGHTNESS_INVALID_FLOAT, /* nits= */ -1,
+                    PowerManager.BRIGHTNESS_INVALID_FLOAT, BrightnessMappingStrategy.INVALID_NITS,
                     /* ambientLux= */ 0, /* slowChange= */ false), mClock.uptimeMillis());
         }
         mDisplayBrightnessFollowers.clear();
@@ -2827,6 +2886,12 @@
                         updatePowerState();
                     }
                     break;
+                case MSG_OFFLOADING_SCREEN_ON_UNBLOCKED:
+                    if (mDisplayOffloadSession == msg.obj) {
+                        unblockScreenOnByDisplayOffload();
+                        updatePowerState();
+                    }
+                    break;
                 case MSG_CONFIGURE_BRIGHTNESS:
                     BrightnessConfiguration brightnessConfiguration =
                             (BrightnessConfiguration) msg.obj;
@@ -3012,7 +3077,7 @@
         AutomaticBrightnessController getAutomaticBrightnessController(
                 AutomaticBrightnessController.Callbacks callbacks, Looper looper,
                 SensorManager sensorManager, Sensor lightSensor,
-                BrightnessMappingStrategy interactiveModeBrightnessMapper,
+                SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap,
                 int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
                 float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
                 long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
@@ -3023,26 +3088,26 @@
                 HysteresisLevels ambientBrightnessThresholdsIdle,
                 HysteresisLevels screenBrightnessThresholdsIdle, Context context,
                 BrightnessRangeController brightnessModeController,
-                BrightnessThrottler brightnessThrottler,
-                BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
-                int ambientLightHorizonLong, float userLux, float userBrightness) {
+                BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort,
+                int ambientLightHorizonLong, float userLux, float userNits) {
             return new AutomaticBrightnessController(callbacks, looper, sensorManager, lightSensor,
-                    interactiveModeBrightnessMapper, lightSensorWarmUpTime, brightnessMin,
+                    brightnessMappingStrategyMap, lightSensorWarmUpTime, brightnessMin,
                     brightnessMax, dozeScaleFactor, lightSensorRate, initialLightSensorRate,
                     brighteningLightDebounceConfig, darkeningLightDebounceConfig,
                     brighteningLightDebounceConfigIdle, darkeningLightDebounceConfigIdle,
                     resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds,
                     screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
                     screenBrightnessThresholdsIdle, context, brightnessModeController,
-                    brightnessThrottler, idleModeBrightnessMapper, ambientLightHorizonShort,
-                    ambientLightHorizonLong, userLux, userBrightness);
+                    brightnessThrottler, ambientLightHorizonShort, ambientLightHorizonLong, userLux,
+                    userNits);
         }
 
-        BrightnessMappingStrategy getInteractiveModeBrightnessMapper(Resources resources,
+        BrightnessMappingStrategy getDefaultModeBrightnessMapper(Resources resources,
                 DisplayDeviceConfig displayDeviceConfig,
                 DisplayWhiteBalanceController displayWhiteBalanceController) {
             return BrightnessMappingStrategy.create(resources,
-                    displayDeviceConfig, displayWhiteBalanceController);
+                    displayDeviceConfig, AUTO_BRIGHTNESS_MODE_DEFAULT,
+                    displayWhiteBalanceController);
         }
 
         HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index 72079a4..c279184 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -69,9 +69,10 @@
 
     /**
      * Used to decide the associated AutomaticBrightnessController's BrightnessMode
-     * @param isIdle Flag which represents if the Idle BrightnessMode is to be set
+     * @param mode The auto-brightness mode
      */
-    void setAutomaticScreenBrightnessMode(boolean isIdle);
+    void setAutomaticScreenBrightnessMode(
+            @AutomaticBrightnessController.AutomaticBrightnessMode int mode);
 
     /**
      * Used to enable/disable the logging of the WhileBalance associated entities
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 22898a6..25576ce 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -25,6 +25,7 @@
 import android.content.res.Resources;
 import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;
 import android.hardware.sidekick.SidekickInternal;
+import android.media.MediaDrm;
 import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
@@ -242,6 +243,10 @@
         private SurfaceControl.DisplayMode mActiveSfDisplayMode;
         // The active display vsync period in SurfaceFlinger
         private float mActiveRenderFrameRate;
+        // The current HDCP level supported by the display, 0 indicates unset
+        // values are defined in hardware/interfaces/drm/aidl/android/hardware/drm/HdcpLevel.aidl
+        private int mConnectedHdcpLevel;
+
         private DisplayEventReceiver.FrameRateOverride[] mFrameRateOverrides =
                 new DisplayEventReceiver.FrameRateOverride[0];
 
@@ -675,8 +680,9 @@
                 mInfo.yDpi = mActiveSfDisplayMode.yDpi;
                 mInfo.deviceProductInfo = mStaticDisplayInfo.deviceProductInfo;
 
-                // Assume that all built-in displays that have secure output (eg. HDCP) also
-                // support compositing from gralloc protected buffers.
+                if (mConnectedHdcpLevel != 0) {
+                    mStaticDisplayInfo.secure = mConnectedHdcpLevel >= MediaDrm.HDCP_V1;
+                }
                 if (mStaticDisplayInfo.secure) {
                     mInfo.flags = DisplayDeviceInfo.FLAG_SECURE
                             | DisplayDeviceInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS;
@@ -1093,6 +1099,12 @@
             }
         }
 
+        public void onHdcpLevelsChangedLocked(int connectedLevel, int maxLevel) {
+            if (updateHdcpLevelsLocked(connectedLevel, maxLevel)) {
+                updateDeviceInfoLocked();
+            }
+        }
+
         public boolean updateActiveModeLocked(int activeSfModeId, float renderFrameRate) {
             if (mActiveSfDisplayMode.id == activeSfModeId
                     && mActiveRenderFrameRate == renderFrameRate) {
@@ -1118,6 +1130,22 @@
             return true;
         }
 
+        public boolean updateHdcpLevelsLocked(int connectedLevel, int maxLevel) {
+            if (connectedLevel > maxLevel) {
+                Slog.w(TAG, "HDCP connected level: " + connectedLevel
+                        + " is larger than max level: " + maxLevel
+                        + ", ignoring request.");
+                return false;
+            }
+
+            if (mConnectedHdcpLevel == connectedLevel) {
+                return false;
+            }
+
+            mConnectedHdcpLevel = connectedLevel;
+            return true;
+        }
+
         public void requestColorModeLocked(int colorMode) {
             if (mActiveColorMode == colorMode) {
                 return;
@@ -1387,6 +1415,7 @@
                 long renderPeriod);
         void onFrameRateOverridesChanged(long timestampNanos, long physicalDisplayId,
                 DisplayEventReceiver.FrameRateOverride[] overrides);
+        void onHdcpLevelsChanged(long physicalDisplayId, int connectedLevel, int maxLevel);
 
     }
 
@@ -1420,6 +1449,11 @@
                 DisplayEventReceiver.FrameRateOverride[] overrides) {
             mListener.onFrameRateOverridesChanged(timestampNanos, physicalDisplayId, overrides);
         }
+
+        @Override
+        public void onHdcpLevelsChanged(long physicalDisplayId, int connectedLevel, int maxLevel) {
+            mListener.onHdcpLevelsChanged(physicalDisplayId, connectedLevel, maxLevel);
+        }
     }
 
     private final class LocalDisplayEventListener implements DisplayEventListener {
@@ -1489,6 +1523,26 @@
                 device.onFrameRateOverridesChanged(overrides);
             }
         }
+
+        @Override
+        public void onHdcpLevelsChanged(long physicalDisplayId, int connectedLevel, int maxLevel) {
+            if (DEBUG) {
+                Slog.d(TAG, "onHdcpLevelsChanged(physicalDisplayId=" + physicalDisplayId
+                        + ", connectedLevel=" + connectedLevel + ", maxLevel=" + maxLevel + ")");
+            }
+            synchronized (getSyncRoot()) {
+                LocalDisplayDevice device = mDevices.get(physicalDisplayId);
+                if (device == null) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Received hdcp levels change for unhandled physical display: "
+                                + "physicalDisplayId=" + physicalDisplayId);
+                    }
+                    return;
+                }
+
+                device.onHdcpLevelsChangedLocked(connectedLevel, maxLevel);
+            }
+        }
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index 617befb..3bb7986 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -27,6 +27,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.display.AutomaticBrightnessController;
+import com.android.server.display.BrightnessMappingStrategy;
 import com.android.server.display.BrightnessSetting;
 import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
@@ -359,11 +360,12 @@
      * passing the brightness value to follower displays.
      *
      * @param brightness The float scale value
-     * @return The nit value or -1f if no conversion is possible.
+     * @return The nit value or {@link BrightnessMappingStrategy.INVALID_NITS} if no conversion is
+     * possible.
      */
     public float convertToNits(float brightness) {
         if (mAutomaticBrightnessController == null) {
-            return -1f;
+            return BrightnessMappingStrategy.INVALID_NITS;
         }
         return mAutomaticBrightnessController.convertToNits(brightness);
     }
@@ -374,11 +376,12 @@
      * {@link com.android.server.display.BrightnessTracker}.
      *
      * @param brightness The float scale value
-     * @return The nit value or -1f if no conversion is possible.
+     * @return The nit value or {@link BrightnessMappingStrategy.INVALID_NITS} if no conversion is
+     * possible.
      */
     public float convertToAdjustedNits(float brightness) {
         if (mAutomaticBrightnessController == null) {
-            return -1f;
+            return BrightnessMappingStrategy.INVALID_NITS;
         }
         return mAutomaticBrightnessController.convertToAdjustedNits(brightness);
     }
@@ -391,11 +394,11 @@
      * @return The float scale value or {@link PowerManager.BRIGHTNESS_INVALID_FLOAT} if no
      * conversion is possible.
      */
-    public float convertToFloatScale(float nits) {
+    public float getBrightnessFromNits(float nits) {
         if (mAutomaticBrightnessController == null) {
             return PowerManager.BRIGHTNESS_INVALID_FLOAT;
         }
-        return mAutomaticBrightnessController.convertToFloatScale(nits);
+        return mAutomaticBrightnessController.getBrightnessFromNits(nits);
     }
 
     /**
@@ -497,7 +500,7 @@
             float brightnessNitsForDefaultDisplay =
                     mBrightnessSetting.getBrightnessNitsForDefaultDisplay();
             if (brightnessNitsForDefaultDisplay >= 0) {
-                float brightnessForDefaultDisplay = convertToFloatScale(
+                float brightnessForDefaultDisplay = getBrightnessFromNits(
                         brightnessNitsForDefaultDisplay);
                 if (BrightnessUtils.isValidBrightnessValue(brightnessForDefaultDisplay)) {
                     mBrightnessSetting.setBrightness(brightnessForDefaultDisplay);
diff --git a/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java b/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java
new file mode 100644
index 0000000..2162850
--- /dev/null
+++ b/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.config;
+
+import android.content.Context;
+import android.os.PowerManager;
+import android.util.Spline;
+
+import com.android.internal.display.BrightnessSynchronizer;
+import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.feature.DisplayManagerFlags;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Provides a mapping between lux and brightness values in order to support auto-brightness.
+ */
+public class DisplayBrightnessMappingConfig {
+
+    private static final String DEFAULT_BRIGHTNESS_MAPPING_KEY = "default_normal";
+
+    /**
+     * Array of desired screen brightness in nits corresponding to the lux values
+     * in the mBrightnessLevelsLuxMap.get(DEFAULT_ID) array. The display brightness is defined as
+     * the measured brightness of an all-white image. The brightness values must be non-negative and
+     * non-decreasing. This must be overridden in platform specific overlays
+     */
+    private float[] mBrightnessLevelsNits;
+
+    /**
+     * Map of arrays of desired screen brightness corresponding to the lux values
+     * in mBrightnessLevelsLuxMap, indexed by the auto-brightness mode and the brightness setting.
+     * The brightness values must be non-negative and non-decreasing. They must be between
+     * {@link PowerManager.BRIGHTNESS_MIN} and {@link PowerManager.BRIGHTNESS_MAX}.
+     *
+     * The keys are a concatenation of the auto-brightness mode and the brightness setting
+     * separated by an underscore, e.g. default_normal, default_dim, default_bright, doze_normal,
+     * doze_dim, doze_bright.
+     */
+    private final Map<String, float[]> mBrightnessLevelsMap = new HashMap<>();
+
+    /**
+     * Map of arrays of light sensor lux values to define our levels for auto-brightness support,
+     * indexed by the auto-brightness mode and the brightness setting.
+     *
+     * The first lux value in every array is always 0.
+     *
+     * The control points must be strictly increasing. Each control point corresponds to an entry
+     * in the brightness values arrays. For example, if lux == luxLevels[1] (second element
+     * of the levels array) then the brightness will be determined by brightnessLevels[1] (second
+     * element of the brightness values array).
+     *
+     * Spline interpolation is used to determine the auto-brightness values for lux levels between
+     * these control points.
+     *
+     * The keys are a concatenation of the auto-brightness mode and the brightness setting
+     * separated by an underscore, e.g. default_normal, default_dim, default_bright, doze_normal,
+     * doze_dim, doze_bright.
+     */
+    private final Map<String, float[]> mBrightnessLevelsLuxMap = new HashMap<>();
+
+    /**
+     * Loads the auto-brightness display brightness mappings. Internally, this takes care of
+     * loading the value from the display config, and if not present, falls back to config.xml.
+     */
+    public DisplayBrightnessMappingConfig(Context context, DisplayManagerFlags flags,
+            AutoBrightness autoBrightnessConfig, Spline backlightToBrightnessSpline) {
+        if (flags.areAutoBrightnessModesEnabled() && autoBrightnessConfig != null
+                && autoBrightnessConfig.getLuxToBrightnessMapping() != null
+                && autoBrightnessConfig.getLuxToBrightnessMapping().size() > 0) {
+            for (LuxToBrightnessMapping mapping
+                    : autoBrightnessConfig.getLuxToBrightnessMapping()) {
+                final int size = mapping.getMap().getPoint().size();
+                float[] brightnessLevels = new float[size];
+                float[] brightnessLevelsLux = new float[size];
+                for (int i = 0; i < size; i++) {
+                    float backlight = mapping.getMap().getPoint().get(i).getSecond().floatValue();
+                    brightnessLevels[i] = backlightToBrightnessSpline.interpolate(backlight);
+                    brightnessLevelsLux[i] = mapping.getMap().getPoint().get(i).getFirst()
+                            .floatValue();
+                }
+                if (size == 0) {
+                    throw new IllegalArgumentException(
+                            "A display brightness mapping should not be empty");
+                }
+                if (brightnessLevelsLux[0] != 0) {
+                    throw new IllegalArgumentException(
+                            "The first lux value in the display brightness mapping must be 0");
+                }
+
+                String key = (mapping.getMode() == null ? "default" : mapping.getMode()) + "_"
+                        + (mapping.getSetting() == null ? "normal" : mapping.getSetting());
+                if (mBrightnessLevelsMap.containsKey(key)
+                        || mBrightnessLevelsLuxMap.containsKey(key)) {
+                    throw new IllegalArgumentException(
+                            "A display brightness mapping with key " + key + " already exists");
+                }
+                mBrightnessLevelsMap.put(key, brightnessLevels);
+                mBrightnessLevelsLuxMap.put(key, brightnessLevelsLux);
+            }
+        }
+
+        if (!mBrightnessLevelsMap.containsKey(DEFAULT_BRIGHTNESS_MAPPING_KEY)
+                || !mBrightnessLevelsLuxMap.containsKey(DEFAULT_BRIGHTNESS_MAPPING_KEY)) {
+            mBrightnessLevelsNits = DisplayDeviceConfig.getFloatArray(context.getResources()
+                    .obtainTypedArray(com.android.internal.R.array
+                            .config_autoBrightnessDisplayValuesNits), PowerManager
+                    .BRIGHTNESS_OFF_FLOAT);
+
+            float[] brightnessLevelsLux = DisplayDeviceConfig.getLuxLevels(context.getResources()
+                    .getIntArray(com.android.internal.R.array
+                            .config_autoBrightnessLevels));
+            mBrightnessLevelsLuxMap.put(DEFAULT_BRIGHTNESS_MAPPING_KEY, brightnessLevelsLux);
+
+            // Load the old configuration in the range [0, 255]. The values need to be normalized
+            // to the range [0, 1].
+            int[] brightnessLevels = context.getResources().getIntArray(
+                    com.android.internal.R.array.config_autoBrightnessLcdBacklightValues);
+            mBrightnessLevelsMap.put(DEFAULT_BRIGHTNESS_MAPPING_KEY,
+                    brightnessArrayIntToFloat(brightnessLevels, backlightToBrightnessSpline));
+        }
+    }
+
+    /**
+     * @return The default auto-brightness brightening ambient lux levels
+     */
+    public float[] getLuxArray() {
+        return mBrightnessLevelsLuxMap.get(DEFAULT_BRIGHTNESS_MAPPING_KEY);
+    }
+
+    /**
+     * @param mode The auto-brightness mode
+     * @param setting The brightness setting
+     * @return Auto brightness brightening ambient lux levels for the specified mode and setting
+     */
+    public float[] getLuxArray(String mode, String setting) {
+        return mBrightnessLevelsLuxMap.get(mode + "_" + setting);
+    }
+
+    /**
+     * @return Auto brightness brightening nits levels
+     */
+    public float[] getNitsArray() {
+        return mBrightnessLevelsNits;
+    }
+
+    /**
+     * @return The default auto-brightness brightening levels
+     */
+    public float[] getBrightnessArray() {
+        return mBrightnessLevelsMap.get(DEFAULT_BRIGHTNESS_MAPPING_KEY);
+    }
+
+    /**
+     * @param mode The auto-brightness mode
+     * @param setting The brightness setting
+     * @return Auto brightness brightening ambient lux levels for the specified mode and setting
+     */
+    public float[] getBrightnessArray(String mode, String setting) {
+        return mBrightnessLevelsMap.get(mode + "_" + setting);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder brightnessLevelsLuxMapString = new StringBuilder("{");
+        for (Map.Entry<String, float[]> entry : mBrightnessLevelsLuxMap.entrySet()) {
+            brightnessLevelsLuxMapString.append(entry.getKey()).append("=").append(
+                    Arrays.toString(entry.getValue())).append(", ");
+        }
+        if (brightnessLevelsLuxMapString.length() > 2) {
+            brightnessLevelsLuxMapString.delete(brightnessLevelsLuxMapString.length() - 2,
+                    brightnessLevelsLuxMapString.length());
+        }
+        brightnessLevelsLuxMapString.append("}");
+
+        StringBuilder brightnessLevelsMapString = new StringBuilder("{");
+        for (Map.Entry<String, float[]> entry : mBrightnessLevelsMap.entrySet()) {
+            brightnessLevelsMapString.append(entry.getKey()).append("=").append(
+                    Arrays.toString(entry.getValue())).append(", ");
+        }
+        if (brightnessLevelsMapString.length() > 2) {
+            brightnessLevelsMapString.delete(brightnessLevelsMapString.length() - 2,
+                    brightnessLevelsMapString.length());
+        }
+        brightnessLevelsMapString.append("}");
+
+        return "mBrightnessLevelsNits= " + Arrays.toString(mBrightnessLevelsNits)
+                + ", mBrightnessLevelsLuxMap= " + brightnessLevelsLuxMapString
+                + ", mBrightnessLevelsMap= " + brightnessLevelsMapString;
+    }
+
+    private float[] brightnessArrayIntToFloat(int[] brightnessInt,
+            Spline backlightToBrightnessSpline) {
+        float[] brightnessFloat = new float[brightnessInt.length];
+        for (int i = 0; i < brightnessInt.length; i++) {
+            brightnessFloat[i] = backlightToBrightnessSpline.interpolate(
+                    BrightnessSynchronizer.brightnessIntToFloat(brightnessInt[i]));
+        }
+        return brightnessFloat;
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index 4089a81..dda50ca 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -167,13 +167,17 @@
 
     /**
      * Indicates that the IME window has re-parented to the new target when the IME control changed.
+     *
+     * @param displayId the display hosting the IME window
      */
-    public abstract void onImeParentChanged();
+    public abstract void onImeParentChanged(int displayId);
 
     /**
-     * Destroys the IME surface.
+     * Destroys the IME surface for the given display.
+     *
+     * @param displayId the display hosting the IME window
      */
-    public abstract void removeImeSurface();
+    public abstract void removeImeSurface(int displayId);
 
     /**
      * Updates the IME visibility, back disposition and show IME picker status for SystemUI.
@@ -298,11 +302,11 @@
                 }
 
                 @Override
-                public void onImeParentChanged() {
+                public void onImeParentChanged(int displayId) {
                 }
 
                 @Override
-                public void removeImeSurface() {
+                public void removeImeSurface(int displayId) {
                 }
 
                 @Override
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 16e043c..0d29b7d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -5671,7 +5671,7 @@
         }
 
         @Override
-        public void onImeParentChanged() {
+        public void onImeParentChanged(int displayId) {
             synchronized (ImfLock.class) {
                 // Hide the IME method menu only when the IME surface parent is changed by the
                 // input target changed, in case seeing the dialog dismiss flickering during
@@ -5683,7 +5683,7 @@
         }
 
         @Override
-        public void removeImeSurface() {
+        public void removeImeSurface(int displayId) {
             mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE).sendToTarget();
         }
 
diff --git a/services/core/java/com/android/server/location/altitude/AltitudeService.java b/services/core/java/com/android/server/location/altitude/AltitudeService.java
index b321e4d..97bf354 100644
--- a/services/core/java/com/android/server/location/altitude/AltitudeService.java
+++ b/services/core/java/com/android/server/location/altitude/AltitudeService.java
@@ -19,6 +19,8 @@
 import android.content.Context;
 import android.frameworks.location.altitude.AddMslAltitudeToLocationRequest;
 import android.frameworks.location.altitude.AddMslAltitudeToLocationResponse;
+import android.frameworks.location.altitude.GetGeoidHeightRequest;
+import android.frameworks.location.altitude.GetGeoidHeightResponse;
 import android.frameworks.location.altitude.IAltitudeService;
 import android.location.Location;
 import android.location.altitude.AltitudeConverter;
@@ -52,15 +54,47 @@
         location.setLongitude(request.longitudeDegrees);
         location.setAltitude(request.altitudeMeters);
         location.setVerticalAccuracyMeters(request.verticalAccuracyMeters);
+
+        AddMslAltitudeToLocationResponse response = new AddMslAltitudeToLocationResponse();
         try {
             mAltitudeConverter.addMslAltitudeToLocation(mContext, location);
         } catch (IOException e) {
-            throw new RemoteException(e);
+            response.success = false;
+            return response;
         }
-
-        AddMslAltitudeToLocationResponse response = new AddMslAltitudeToLocationResponse();
         response.mslAltitudeMeters = location.getMslAltitudeMeters();
         response.mslAltitudeAccuracyMeters = location.getMslAltitudeAccuracyMeters();
+        response.success = true;
+        return response;
+    }
+
+    @Override
+    public GetGeoidHeightResponse getGeoidHeight(GetGeoidHeightRequest request)
+            throws RemoteException {
+        Location location = new Location("");
+        location.setLatitude(request.latitudeDegrees);
+        location.setLongitude(request.longitudeDegrees);
+        location.setAltitude(0.0);
+        location.setVerticalAccuracyMeters(0.0f);
+
+        GetGeoidHeightResponse response = new GetGeoidHeightResponse();
+        try {
+            mAltitudeConverter.addMslAltitudeToLocation(mContext, location);
+        } catch (IOException e) {
+            response.success = false;
+            return response;
+        }
+        // The geoid height for a location with zero WGS84 altitude is equal in value to the
+        // negative of the corresponding MSL altitude.
+        response.geoidHeightMeters = -location.getMslAltitudeMeters();
+        // The geoid height error for a location with zero vertical accuracy is equal in value to
+        // the corresponding MSL altitude accuracy.
+        response.geoidHeightErrorMeters = location.getMslAltitudeAccuracyMeters();
+        // The expiration distance and additional error are currently set to constants used by
+        // health services.
+        response.expirationDistanceMeters = 10000.0;
+        response.additionalGeoidHeightErrorMeters = 0.707f;
+        response.success = true;
         return response;
     }
 
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index d3eb5a9..91e6a80 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -1734,12 +1734,6 @@
             return null;
         }
 
-        // lastly - note app ops
-        if (!mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(permissionLevel),
-                identity)) {
-            return null;
-        }
-
         Location location = getPermittedLocation(
                 getLastLocationUnsafe(
                         identity.getUserId(),
@@ -1748,10 +1742,18 @@
                         Long.MAX_VALUE),
                 permissionLevel);
 
-        if (location != null && identity.getPid() == Process.myPid()) {
+        if (location != null) {
+            // lastly - note app ops
+            if (!mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(permissionLevel),
+                    identity)) {
+                return null;
+            }
+
             // if delivering to the same process, make a copy of the location first (since
             // location is mutable)
-            location = new Location(location);
+            if (identity.getPid() == Process.myPid()) {
+                location = new Location(location);
+            }
         }
 
         return location;
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index da01745..55d8a0f 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -33,4 +33,11 @@
   namespace: "systemui"
   description: "This flag controls the vibrate while unlocked setting of polite notifications"
   bug: "270456865"
-}
\ No newline at end of file
+}
+
+flag {
+  name: "autogroup_summary_icon_update"
+  namespace: "systemui"
+  description: "This flag controls the fix for notifications autogroup summary icon updates"
+  bug: "227693160"
+}
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index 9ce3cb3..f6e7ef3 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -41,13 +41,14 @@
 import android.system.StructStat;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoParseException;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.BootReceiver;
 import com.android.server.ServiceThread;
 import com.android.server.os.TombstoneProtos.Cause;
 import com.android.server.os.TombstoneProtos.Tombstone;
-import com.android.server.os.protobuf.CodedInputStream;
 
 import libcore.io.IoUtils;
 
@@ -129,21 +130,18 @@
             return;
         }
 
+        String processName = "UNKNOWN";
         final boolean isProtoFile = filename.endsWith(".pb");
-        if (!isProtoFile) {
-            return;
-        }
+        File protoPath = isProtoFile ? path : new File(path.getAbsolutePath() + ".pb");
 
-        Optional<ParsedTombstone> parsedTombstone = handleProtoTombstone(path, true);
+        Optional<TombstoneFile> parsedTombstone = handleProtoTombstone(protoPath, isProtoFile);
         if (parsedTombstone.isPresent()) {
-            BootReceiver.addTombstoneToDropBox(
-                    mContext, path, parsedTombstone.get().getTombstone(),
-                    parsedTombstone.get().getProcessName(), mTmpFileLock);
+            processName = parsedTombstone.get().getProcessName();
         }
+        BootReceiver.addTombstoneToDropBox(mContext, path, isProtoFile, processName, mTmpFileLock);
     }
 
-    private Optional<ParsedTombstone> handleProtoTombstone(
-            File path, boolean addToList) {
+    private Optional<TombstoneFile> handleProtoTombstone(File path, boolean addToList) {
         final String filename = path.getName();
         if (!filename.endsWith(".pb")) {
             Slog.w(TAG, "unexpected tombstone name: " + path);
@@ -173,7 +171,7 @@
             return Optional.empty();
         }
 
-        final Optional<ParsedTombstone> parsedTombstone = TombstoneFile.parse(pfd);
+        final Optional<TombstoneFile> parsedTombstone = TombstoneFile.parse(pfd);
         if (!parsedTombstone.isPresent()) {
             IoUtils.closeQuietly(pfd);
             return Optional.empty();
@@ -186,7 +184,7 @@
                     previous.dispose();
                 }
 
-                mTombstones.put(number, parsedTombstone.get().getTombstoneFile());
+                mTombstones.put(number, parsedTombstone.get());
             }
         }
 
@@ -334,27 +332,6 @@
         }
     }
 
-    static class ParsedTombstone {
-        TombstoneFile mTombstoneFile;
-        Tombstone mTombstone;
-        ParsedTombstone(TombstoneFile tombstoneFile, Tombstone tombstone) {
-            mTombstoneFile = tombstoneFile;
-            mTombstone = tombstone;
-        }
-
-        public String getProcessName() {
-            return mTombstoneFile.getProcessName();
-        }
-
-        public TombstoneFile getTombstoneFile() {
-            return mTombstoneFile;
-        }
-
-        public Tombstone getTombstone() {
-            return mTombstone;
-        }
-    }
-
     static class TombstoneFile {
         final ParcelFileDescriptor mPfd;
 
@@ -437,21 +414,67 @@
             }
         }
 
-        static Optional<ParsedTombstone> parse(ParcelFileDescriptor pfd) {
-            Tombstone tombstoneProto;
-            try (FileInputStream is = new FileInputStream(pfd.getFileDescriptor())) {
-                final byte[] tombstoneBytes = is.readAllBytes();
+        static Optional<TombstoneFile> parse(ParcelFileDescriptor pfd) {
+            final FileInputStream is = new FileInputStream(pfd.getFileDescriptor());
+            final ProtoInputStream stream = new ProtoInputStream(is);
 
-                tombstoneProto = Tombstone.parseFrom(
-                        CodedInputStream.newInstance(tombstoneBytes));
-            } catch (IOException ex) {
+            int pid = 0;
+            int uid = 0;
+            String processName = null;
+            String crashReason = "";
+            String selinuxLabel = "";
+
+            try {
+                while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                    switch (stream.getFieldNumber()) {
+                        case (int) Tombstone.PID:
+                            pid = stream.readInt(Tombstone.PID);
+                            break;
+
+                        case (int) Tombstone.UID:
+                            uid = stream.readInt(Tombstone.UID);
+                            break;
+
+                        case (int) Tombstone.COMMAND_LINE:
+                            if (processName == null) {
+                                processName = stream.readString(Tombstone.COMMAND_LINE);
+                            }
+                            break;
+
+                        case (int) Tombstone.CAUSES:
+                            if (!crashReason.equals("")) {
+                                // Causes appear in decreasing order of likelihood. For now we only
+                                // want the most likely crash reason here, so ignore all others.
+                                break;
+                            }
+                            long token = stream.start(Tombstone.CAUSES);
+                        cause:
+                            while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                                switch (stream.getFieldNumber()) {
+                                    case (int) Cause.HUMAN_READABLE:
+                                        crashReason = stream.readString(Cause.HUMAN_READABLE);
+                                        break cause;
+
+                                    default:
+                                        break;
+                                }
+                            }
+                            stream.end(token);
+                            break;
+
+                        case (int) Tombstone.SELINUX_LABEL:
+                            selinuxLabel = stream.readString(Tombstone.SELINUX_LABEL);
+                            break;
+
+                        default:
+                            break;
+                    }
+                }
+            } catch (IOException | ProtoParseException ex) {
                 Slog.e(TAG, "Failed to parse tombstone", ex);
                 return Optional.empty();
             }
 
-            int pid = tombstoneProto.getPid();
-            int uid = tombstoneProto.getUid();
-
             if (!UserHandle.isApp(uid)) {
                 Slog.e(TAG, "Tombstone's UID (" + uid + ") not an app, ignoring");
                 return Optional.empty();
@@ -468,7 +491,6 @@
             final int userId = UserHandle.getUserId(uid);
             final int appId = UserHandle.getAppId(uid);
 
-            String selinuxLabel = tombstoneProto.getSelinuxLabel();
             if (!selinuxLabel.startsWith("u:r:untrusted_app")) {
                 Slog.e(TAG, "Tombstone has invalid selinux label (" + selinuxLabel + "), ignoring");
                 return Optional.empty();
@@ -480,30 +502,11 @@
             result.mAppId = appId;
             result.mPid = pid;
             result.mUid = uid;
-            result.mProcessName = getCmdLineProcessName(tombstoneProto);
+            result.mProcessName = processName == null ? "" : processName;
             result.mTimestampMs = timestampMs;
-            result.mCrashReason = getCrashReason(tombstoneProto);
+            result.mCrashReason = crashReason;
 
-            return Optional.of(new ParsedTombstone(result, tombstoneProto));
-        }
-
-        private static String getCmdLineProcessName(Tombstone tombstoneProto) {
-            for (String cmdline : tombstoneProto.getCommandLineList()) {
-                if (cmdline != null) {
-                    return cmdline;
-                }
-            }
-            return "";
-        }
-
-        private static String getCrashReason(Tombstone tombstoneProto) {
-            for (Cause cause : tombstoneProto.getCausesList()) {
-                if (cause.getHumanReadable() != null
-                        && !cause.getHumanReadable().equals("")) {
-                    return cause.getHumanReadable();
-                }
-            }
-            return "";
+            return Optional.of(result);
         }
 
         public IParcelFileDescriptorRetriever getPfdRetriever() {
diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java
index 82622d9..f3df424 100644
--- a/services/core/java/com/android/server/pm/AppsFilterImpl.java
+++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java
@@ -1042,7 +1042,9 @@
                 existingSettings, forceQueryable, protectedBroadcasts);
         SparseSetArray<Integer> queriesViaComponent = computer.execute();
         synchronized (mQueriesViaComponentLock) {
-            mQueriesViaComponent.copyFrom(queriesViaComponent);
+            mQueriesViaComponent = new WatchedSparseSetArray<>(queriesViaComponent);
+            mQueriesViaComponentSnapshot = new SnapshotCache.Auto<>(
+                    mQueriesViaComponent, mQueriesViaComponent, "AppsFilter.mQueriesViaComponent");
         }
 
         mQueriesViaComponentRequireRecompute.set(false);
diff --git a/services/core/java/com/android/server/pm/CleanUpArgs.java b/services/core/java/com/android/server/pm/CleanUpArgs.java
new file mode 100644
index 0000000..9da76d6
--- /dev/null
+++ b/services/core/java/com/android/server/pm/CleanUpArgs.java
@@ -0,0 +1,62 @@
+/*
+ * 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.pm;
+
+import android.annotation.NonNull;
+
+import java.io.File;
+
+final class CleanUpArgs {
+    @NonNull
+    private final String mPackageName;
+    @NonNull
+    private final File mCodeFile;
+    @NonNull
+    private final String[] mInstructionSets;
+
+    /**
+     * Create args that describe an existing installed package. Typically used
+     * when cleaning up old installs.
+     */
+    CleanUpArgs(@NonNull String packageName, @NonNull String codePath,
+                @NonNull String[] instructionSets) {
+        mPackageName = packageName;
+        mCodeFile = new File(codePath);
+        mInstructionSets = instructionSets;
+    }
+
+    @NonNull
+    String getPackageName() {
+        return mPackageName;
+    }
+
+    @NonNull
+    File getCodeFile() {
+        return mCodeFile;
+    }
+
+    @NonNull
+    /** @see PackageSetting#getPath() */
+    String getCodePath() {
+        return mCodeFile.getAbsolutePath();
+    }
+
+    @NonNull
+    String[] getInstructionSets() {
+        return mInstructionSets;
+    }
+}
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index 27f4e11..482807c 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -518,7 +518,9 @@
     boolean isPackageStoppedForUser(@NonNull String packageName, @UserIdInt int userId)
             throws PackageManager.NameNotFoundException;
 
-    boolean isSuspendingAnyPackages(@NonNull String suspendingPackage, @UserIdInt int userId);
+    /** Check if the package is suspending any package. */
+    boolean isSuspendingAnyPackages(@NonNull String suspendingPackage,
+            @UserIdInt int suspendingUserId, int targetUserId);
 
     @NonNull
     ParceledListSlice<IntentFilter> getAllIntentFilters(@NonNull String packageName);
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 744c946..3cb2420 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -96,6 +96,7 @@
 import android.content.pm.SigningDetails;
 import android.content.pm.SigningInfo;
 import android.content.pm.UserInfo;
+import android.content.pm.UserPackage;
 import android.content.pm.VersionedPackage;
 import android.os.Binder;
 import android.os.Build;
@@ -5008,11 +5009,13 @@
 
     @Override
     public boolean isSuspendingAnyPackages(@NonNull String suspendingPackage,
-            @UserIdInt int userId) {
+            @UserIdInt int suspendingUserId, int targetUserId) {
+        final UserPackage suspender = UserPackage.of(suspendingUserId, suspendingPackage);
         for (final PackageStateInternal packageState : getPackageStates().values()) {
-            final PackageUserStateInternal state = packageState.getUserStateOrDefault(userId);
+            final PackageUserStateInternal state =
+                    packageState.getUserStateOrDefault(targetUserId);
             if (state.getSuspendParams() != null
-                    && state.getSuspendParams().containsKey(suspendingPackage)) {
+                    && state.getSuspendParams().containsKey(suspender)) {
                 return true;
             }
         }
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index aa7f0d3..b96b704 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -282,8 +282,8 @@
         // other processes clean up before deleting resources.
         synchronized (mPm.mInstallLock) {
             if (info.mArgs != null) {
-                mRemovePackageHelper.cleanUpResources(info.mArgs.mCodeFile,
-                        info.mArgs.mInstructionSets);
+                mRemovePackageHelper.cleanUpResources(info.mArgs.getPackageName(),
+                        info.mArgs.getCodeFile(), info.mArgs.getInstructionSets());
             }
 
             boolean reEnableStub = false;
@@ -571,7 +571,7 @@
 
         // Delete application code and resources only for parent packages
         if (deleteCodeAndResources) {
-            outInfo.mArgs = new InstallArgs(
+            outInfo.mArgs = new CleanUpArgs(ps.getName(),
                     ps.getPathString(), getAppDexInstructionSets(
                             ps.getPrimaryCpuAbiLegacy(), ps.getSecondaryCpuAbiLegacy()));
             if (DEBUG_SD_INSTALL) Slog.i(TAG, "args=" + outInfo.mArgs);
diff --git a/services/core/java/com/android/server/pm/InstallArgs.java b/services/core/java/com/android/server/pm/InstallArgs.java
index cc6bb00..46f9732 100644
--- a/services/core/java/com/android/server/pm/InstallArgs.java
+++ b/services/core/java/com/android/server/pm/InstallArgs.java
@@ -16,14 +16,9 @@
 
 package com.android.server.pm;
 
-import static android.app.AppOpsManager.MODE_DEFAULT;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.pm.DataLoaderType;
 import android.content.pm.IPackageInstallObserver2;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageManager;
 import android.content.pm.SigningDetails;
 import android.os.UserHandle;
 import android.util.ArrayMap;
@@ -102,22 +97,4 @@
         mPackageSource = packageSource;
         mApplicationEnabledSettingPersistent = applicationEnabledSettingPersistent;
     }
-
-    /**
-     * Create args that describe an existing installed package. Typically used
-     * when cleaning up old installs, or used as a move source.
-     */
-    InstallArgs(String codePath, String[] instructionSets) {
-        this(OriginInfo.fromNothing(), null, null, 0, 0, InstallSource.EMPTY, null, null,
-                instructionSets, null, new ArrayMap<>(), null, MODE_DEFAULT, null, 0,
-                SigningDetails.UNKNOWN, PackageManager.INSTALL_REASON_UNKNOWN,
-                PackageManager.INSTALL_SCENARIO_DEFAULT, false, DataLoaderType.NONE,
-                PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, false);
-        mCodeFile = (codePath != null) ? new File(codePath) : null;
-    }
-
-    /** @see PackageSettingBase#getPath() */
-    String getCodePath() {
-        return (mCodeFile != null) ? mCodeFile.getAbsolutePath() : null;
-    }
 }
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 0fa7aa5..65bfb2f 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2111,7 +2111,8 @@
                         // We didn't need to disable the .apk as a current system package,
                         // which means we are replacing another update that is already
                         // installed.  We need to make sure to delete the older one's .apk.
-                        installRequest.getRemovedInfo().mArgs = new InstallArgs(
+                        installRequest.getRemovedInfo().mArgs = new CleanUpArgs(
+                                packageName,
                                 oldPackage.getPath(),
                                 getAppDexInstructionSets(
                                         deletedPkgSetting.getPrimaryCpuAbi(),
@@ -2820,7 +2821,7 @@
             request.setReturnMessage("Package was removed before install could complete.");
 
             // Remove the update failed package's older resources safely now
-            mRemovePackageHelper.cleanUpResources(request.getOldCodeFile(),
+            mRemovePackageHelper.cleanUpResources(packageName, request.getOldCodeFile(),
                     request.getOldInstructionSet());
             mPm.notifyInstallObserver(request);
             return;
@@ -2872,7 +2873,7 @@
                     getUnknownSourcesSettings());
 
             // Remove the replaced package's older resources safely now
-            InstallArgs args = request.getRemovedInfo() != null
+            CleanUpArgs args = request.getRemovedInfo() != null
                     ? request.getRemovedInfo().mArgs : null;
             if (args != null) {
                 if (!killApp) {
@@ -2883,7 +2884,8 @@
                     // propagated to all application threads.
                     mPm.scheduleDeferredNoKillPostDelete(args);
                 } else {
-                    mRemovePackageHelper.cleanUpResources(args.mCodeFile, args.mInstructionSets);
+                    mRemovePackageHelper.cleanUpResources(packageName, args.getCodeFile(),
+                            args.getInstructionSets());
                 }
             } else {
                 // Force a gc to clear up things. Ask for a background one, it's fine to go on
@@ -4040,7 +4042,7 @@
                             + "; " + pkgSetting.getPathString()
                             + " --> " + parsedPackage.getPath());
 
-            mRemovePackageHelper.cleanUpResources(
+            mRemovePackageHelper.cleanUpResources(pkgSetting.getPackageName(),
                     new File(pkgSetting.getPathString()),
                     getAppDexInstructionSets(pkgSetting.getPrimaryCpuAbiLegacy(),
                             pkgSetting.getSecondaryCpuAbiLegacy()));
@@ -4146,7 +4148,8 @@
                                 + parsedPackage.getLongVersionCode()
                                 + "; " + pkgSetting.getPathString() + " --> "
                                 + parsedPackage.getPath());
-                mRemovePackageHelper.cleanUpResources(new File(pkgSetting.getPathString()),
+                mRemovePackageHelper.cleanUpResources(pkgSetting.getPackageName(),
+                        new File(pkgSetting.getPathString()),
                         getAppDexInstructionSets(
                                 pkgSetting.getPrimaryCpuAbiLegacy(), pkgSetting.getSecondaryCpuAbiLegacy()));
             } else {
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index ee780d9..ebaf516 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -294,13 +294,13 @@
     @Nullable
     public File getOldCodeFile() {
         return (mRemovedInfo != null && mRemovedInfo.mArgs != null)
-                ? mRemovedInfo.mArgs.mCodeFile : null;
+                ? mRemovedInfo.mArgs.getCodeFile() : null;
     }
 
     @Nullable
     public String[] getOldInstructionSet() {
         return (mRemovedInfo != null && mRemovedInfo.mArgs != null)
-                ? mRemovedInfo.mArgs.mInstructionSets : null;
+                ? mRemovedInfo.mArgs.getInstructionSets() : null;
     }
 
     public UserHandle getUser() {
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 2864a8b..dcfc855d 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -132,6 +132,11 @@
     private static final String EXTRA_INSTALLER_TITLE =
             "com.android.content.pm.extra.UNARCHIVE_INSTALLER_TITLE";
 
+    private static final PorterDuffColorFilter OPACITY_LAYER_FILTER =
+            new PorterDuffColorFilter(
+                    Color.argb(0.5f /* alpha */, 0f /* red */, 0f /* green */, 0f /* blue */),
+                    PorterDuff.Mode.SRC_ATOP);
+
     private final Context mContext;
     private final PackageManagerService mPm;
 
@@ -746,11 +751,7 @@
             return bitmap;
         }
         BitmapDrawable appIconDrawable = new BitmapDrawable(mContext.getResources(), bitmap);
-        PorterDuffColorFilter colorFilter =
-                new PorterDuffColorFilter(
-                        Color.argb(0.32f /* alpha */, 0f /* red */, 0f /* green */, 0f /* blue */),
-                        PorterDuff.Mode.SRC_ATOP);
-        appIconDrawable.setColorFilter(colorFilter);
+        appIconDrawable.setColorFilter(OPACITY_LAYER_FILTER);
         appIconDrawable.setBounds(
                 0 /* left */,
                 0 /* top */,
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index 4ecbd15..ee5875e 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -101,9 +101,10 @@
                 Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "postInstall", msg.arg1);
             } break;
             case DEFERRED_NO_KILL_POST_DELETE: {
-                InstallArgs args = (InstallArgs) msg.obj;
+                CleanUpArgs args = (CleanUpArgs) msg.obj;
                 if (args != null) {
-                    mPm.cleanUpResources(args.mCodeFile, args.mInstructionSets);
+                    mPm.cleanUpResources(args.getPackageName(), args.getCodeFile(),
+                            args.getInstructionSets());
                 }
             } break;
             case DEFERRED_NO_KILL_INSTALL_OBSERVER:
diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
index c737b45..8da1683 100644
--- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
+++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@@ -19,6 +19,8 @@
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
 import static android.content.pm.PackageManager.RESTRICTION_NONE;
 
+import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -37,6 +39,7 @@
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.SuspendDialogInfo;
+import android.content.pm.UserPackage;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -250,7 +253,7 @@
         getSuspendPackageHelper().removeSuspensionsBySuspendingPackage(snapshot(),
                 new String[]{packageName},
                 (suspendingPackage) -> !PackageManagerService.PLATFORM_PACKAGE_NAME.equals(
-                        suspendingPackage),
+                        suspendingPackage.packageName),
                 userId);
     }
 
@@ -269,7 +272,7 @@
 
     @Override
     @Deprecated
-    public final String getSuspendingPackage(String suspendedPackage, int userId) {
+    public final UserPackage getSuspendingPackage(String suspendedPackage, int userId) {
         return getSuspendPackageHelper().getSuspendingPackage(snapshot(), suspendedPackage, userId,
                 Binder.getCallingUid());
     }
@@ -277,7 +280,7 @@
     @Override
     @Deprecated
     public final SuspendDialogInfo getSuspendedDialogInfo(String suspendedPackage,
-            String suspendingPackage, int userId) {
+            UserPackage suspendingPackage, int userId) {
         return getSuspendPackageHelper().getSuspendedDialogInfo(snapshot(), suspendedPackage,
                 suspendingPackage, userId, Binder.getCallingUid());
     }
@@ -683,14 +686,16 @@
 
     @Override
     @Deprecated
-    public final void unsuspendForSuspendingPackage(final String packageName, int affectedUser) {
-        mService.unsuspendForSuspendingPackage(snapshot(), packageName, affectedUser);
+    public final void unsuspendAdminSuspendedPackages(int affectedUser) {
+        final int suspendingUserId = affectedUser;
+        mService.unsuspendForSuspendingPackage(snapshot(), PLATFORM_PACKAGE_NAME, suspendingUserId);
     }
 
     @Override
     @Deprecated
-    public final boolean isSuspendingAnyPackages(String suspendingPackage, int userId) {
-        return snapshot().isSuspendingAnyPackages(suspendingPackage, userId);
+    public final boolean isAdminSuspendingAnyPackages(int userId) {
+        final int suspendingUserId = userId;
+        return snapshot().isSuspendingAnyPackages(PLATFORM_PACKAGE_NAME, suspendingUserId, userId);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 56365b6..1d5b8c3 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -292,6 +292,7 @@
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Consumer;
+import java.util.function.Predicate;
 
 /**
  * Keep track of all those APKs everywhere.
@@ -1293,7 +1294,7 @@
         mHandler.sendMessageDelayed(message, DEFERRED_NO_KILL_INSTALL_OBSERVER_DELAY_MS);
     }
 
-    void scheduleDeferredNoKillPostDelete(InstallArgs args) {
+    void scheduleDeferredNoKillPostDelete(CleanUpArgs args) {
         Message message = mHandler.obtainMessage(DEFERRED_NO_KILL_POST_DELETE, args);
         // If the feature flag is on, retain the old files for a day. Otherwise, delete the old
         // files after a few seconds.
@@ -3137,7 +3138,7 @@
     }
 
     private void enforceCanSetPackagesSuspendedAsUser(@NonNull Computer snapshot,
-            boolean quarantined, String callingPackage, int callingUid, int userId,
+            boolean quarantined, UserPackage suspender, int callingUid, int targetUserId,
             String callingMethod) {
         if (callingUid == Process.ROOT_UID
                 // Need to compare app-id to allow system dialogs access on secondary users
@@ -3145,9 +3146,10 @@
             return;
         }
 
-        final String ownerPackage = mProtectedPackages.getDeviceOwnerOrProfileOwnerPackage(userId);
+        final String ownerPackage =
+                mProtectedPackages.getDeviceOwnerOrProfileOwnerPackage(targetUserId);
         if (ownerPackage != null) {
-            final int ownerUid = snapshot.getPackageUid(ownerPackage, 0, userId);
+            final int ownerUid = snapshot.getPackageUid(ownerPackage, 0, targetUserId);
             if (ownerUid == callingUid) {
                 return;
             }
@@ -3168,25 +3170,27 @@
                     callingMethod);
         }
 
-        final int packageUid = snapshot.getPackageUid(callingPackage, 0, userId);
+        final int packageUid = snapshot.getPackageUid(suspender.packageName, 0, targetUserId);
         final boolean allowedPackageUid = packageUid == callingUid;
         // TODO(b/139383163): remove special casing for shell and enforce INTERACT_ACROSS_USERS_FULL
         final boolean allowedShell = callingUid == SHELL_UID
                 && UserHandle.isSameApp(packageUid, callingUid);
 
         if (!allowedShell && !allowedPackageUid) {
-            throw new SecurityException("Calling package " + callingPackage + " in user "
-                    + userId + " does not belong to calling uid " + callingUid);
+            throw new SecurityException("Suspending package " + suspender.packageName
+                    + " in user " + targetUserId + " does not belong to calling uid " + callingUid);
         }
     }
 
     void unsuspendForSuspendingPackage(@NonNull Computer computer, String suspendingPackage,
-            @UserIdInt int userId) {
+            @UserIdInt int suspendingUserId) {
         // TODO: This can be replaced by a special parameter to iterate all packages, rather than
         //  this weird pre-collect of all packages.
         final String[] allPackages = computer.getPackageStates().keySet().toArray(new String[0]);
+        final Predicate<UserPackage> suspenderPredicate =
+                UserPackage.of(suspendingUserId, suspendingPackage)::equals;
         mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(computer,
-                allPackages, suspendingPackage::equals, userId);
+                allPackages, suspenderPredicate, suspendingUserId);
     }
 
     void removeAllDistractingPackageRestrictions(@NonNull Computer snapshot, int userId) {
@@ -5259,8 +5263,9 @@
                 if (!snapshot.isPackageSuspendedForUser(packageName, userId)) {
                     return null;
                 }
-                return mSuspendPackageHelper.getSuspendingPackage(snapshot, packageName, userId,
-                        callingUid);
+                final UserPackage suspender = mSuspendPackageHelper.getSuspendingPackage(
+                        snapshot, packageName, userId, callingUid);
+                return suspender != null ? suspender.packageName : null;
             } catch (PackageManager.NameNotFoundException e) {
                 return null;
             }
@@ -6198,7 +6203,8 @@
         @Override
         public String[] setPackagesSuspendedAsUser(String[] packageNames, boolean suspended,
                 PersistableBundle appExtras, PersistableBundle launcherExtras,
-                SuspendDialogInfo dialogInfo, int flags, String callingPackage, int userId) {
+                SuspendDialogInfo dialogInfo, int flags, String suspendingPackage,
+                int suspendingUserId, int targetUserId) {
             final int callingUid = Binder.getCallingUid();
             boolean quarantined = false;
             if (Flags.quarantinedEnabled()) {
@@ -6207,14 +6213,15 @@
                 } else if (FeatureFlagUtils.isEnabled(mContext,
                         SETTINGS_TREAT_PAUSE_AS_QUARANTINE)) {
                     final String wellbeingPkg = mContext.getString(R.string.config_systemWellbeing);
-                    quarantined = callingPackage.equals(wellbeingPkg);
+                    quarantined = suspendingPackage.equals(wellbeingPkg);
                 }
             }
             final Computer snapshot = snapshotComputer();
-            enforceCanSetPackagesSuspendedAsUser(snapshot, quarantined, callingPackage, callingUid,
-                    userId, "setPackagesSuspendedAsUser");
+            final UserPackage suspender = UserPackage.of(targetUserId, suspendingPackage);
+            enforceCanSetPackagesSuspendedAsUser(snapshot, quarantined, suspender, callingUid,
+                    targetUserId, "setPackagesSuspendedAsUser");
             return mSuspendPackageHelper.setPackagesSuspended(snapshot, packageNames, suspended,
-                    appExtras, launcherExtras, dialogInfo, callingPackage, userId, callingUid,
+                    appExtras, launcherExtras, dialogInfo, suspender, targetUserId, callingUid,
                     quarantined);
         }
 
@@ -6653,7 +6660,7 @@
             final Computer computer = snapshotComputer();
             final String[] allPackages = computer.getAllAvailablePackageNames();
             mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(computer, allPackages,
-                    (suspendingPackage) -> !PLATFORM_PACKAGE_NAME.equals(suspendingPackage),
+                    (suspender) -> !PLATFORM_PACKAGE_NAME.equals(suspender.packageName),
                     userId);
         }
 
@@ -6667,8 +6674,13 @@
         @Override
         public String[] setPackagesSuspendedByAdmin(
                 @UserIdInt int userId, @NonNull String[] packageNames, boolean suspended) {
-            return mSuspendPackageHelper.setPackagesSuspendedByAdmin(
-                    snapshotComputer(), userId, packageNames, suspended);
+            final int suspendingUserId = userId;
+            final UserPackage suspender = UserPackage.of(
+                    suspendingUserId, PackageManagerService.PLATFORM_PACKAGE_NAME);
+            return mSuspendPackageHelper.setPackagesSuspended(snapshotComputer(), packageNames,
+                    suspended, null /* appExtras */, null /* launcherExtras */,
+                    null /* dialogInfo */, suspender, userId, Process.SYSTEM_UID,
+                    false /* quarantined */);
         }
 
         @Override
@@ -7974,8 +7986,9 @@
         mRemovePackageHelper.removeCodePath(codePath);
     }
 
-    void cleanUpResources(@Nullable File codeFile, @Nullable String[] instructionSets) {
-        mRemovePackageHelper.cleanUpResources(codeFile, instructionSets);
+    void cleanUpResources(@NonNull String packageName, @NonNull File codeFile,
+                          @NonNull String[] instructionSets) {
+        mRemovePackageHelper.cleanUpResources(packageName, codeFile, instructionSets);
     }
 
     void cleanUpForMoveInstall(String volumeUuid, String packageName, String fromCodePath) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 322557b..243fb16 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -2827,7 +2827,7 @@
             mInterface.setPackagesSuspendedAsUser(packageNames.toArray(new String[] {}),
                     suspendedState, ((appExtras.size() > 0) ? appExtras : null),
                     ((launcherExtras.size() > 0) ? launcherExtras : null),
-                    info, flags, callingPackage, translatedUserId);
+                    info, flags, callingPackage, UserHandle.USER_SYSTEM, translatedUserId);
             for (int i = 0; i < packageNames.size(); i++) {
                 final String packageName = packageNames.get(i);
                 pw.println("Package " + packageName + " new suspended state: "
diff --git a/services/core/java/com/android/server/pm/PackageRemovedInfo.java b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
index 881b0b3..6f57538 100644
--- a/services/core/java/com/android/server/pm/PackageRemovedInfo.java
+++ b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
@@ -43,7 +43,7 @@
     // of package changes
     SparseArray<int[]> mBroadcastAllowList;
     // Clean up resources deleted packages.
-    InstallArgs mArgs = null;
+    CleanUpArgs mArgs = null;
     private static final int[] EMPTY_INT_ARRAY = new int[0];
 
     public void populateBroadcastUsers(PackageSetting deletedPackageSetting) {
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 7d0a1f6..02ce159 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -32,6 +32,7 @@
 import android.content.pm.SigningDetails;
 import android.content.pm.SigningInfo;
 import android.content.pm.UserInfo;
+import android.content.pm.UserPackage;
 import android.content.pm.overlay.OverlayPaths;
 import android.os.UserHandle;
 import android.os.incremental.IncrementalManager;
@@ -71,6 +72,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -172,6 +174,8 @@
     private File mPath;
     @NonNull
     private String mPathString;
+    @Nullable
+    private LinkedHashSet<File> mOldPaths;
 
     private float mLoadingProgress;
     private long mLoadingCompletedTime;
@@ -682,6 +686,7 @@
         pkg = other.pkg;
         mPath = other.mPath;
         mPathString = other.mPathString;
+        mOldPaths = other.mOldPaths == null ? null : new LinkedHashSet<>(other.mOldPaths);
         mPrimaryCpuAbi = other.mPrimaryCpuAbi;
         mSecondaryCpuAbi = other.mSecondaryCpuAbi;
         mCpuAbiOverride = other.mCpuAbiOverride;
@@ -952,7 +957,7 @@
 
     void setUserState(int userId, long ceDataInode, long deDataInode, int enabled,
                       boolean installed, boolean stopped, boolean notLaunched, boolean hidden,
-                      int distractionFlags, ArrayMap<String, SuspendParams> suspendParams,
+                      int distractionFlags, ArrayMap<UserPackage, SuspendParams> suspendParams,
                       boolean instantApp, boolean virtualPreload, String lastDisableAppCaller,
                       ArraySet<String> enabledComponents, ArraySet<String> disabledComponents,
                       int installReason, int uninstallReason,
@@ -1182,7 +1187,7 @@
             if (state.isSuspended()) {
                 for (int j = 0; j < state.getSuspendParams().size(); j++) {
                     proto.write(PackageProto.UserInfoProto.SUSPENDING_PACKAGE,
-                            state.getSuspendParams().keyAt(j));
+                            state.getSuspendParams().keyAt(j).packageName);
                 }
             }
             proto.write(PackageProto.UserInfoProto.IS_STOPPED, state.isStopped());
@@ -1237,6 +1242,27 @@
         return this;
     }
 
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public PackageSetting addOldPath(@NonNull File path) {
+        if (mOldPaths == null) {
+            mOldPaths = new LinkedHashSet<>();
+        }
+        mOldPaths.add(path);
+        onChanged();
+        return this;
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public PackageSetting removeOldPath(@Nullable File path) {
+        if (path == null || mOldPaths == null) {
+            return this;
+        }
+        if (mOldPaths.remove(path)) {
+            onChanged();
+        }
+        return this;
+    }
+
     /**
      * @param userId the specific user to change the label/icon for
      * @see PackageUserStateImpl#overrideLabelAndIcon(ComponentName, String, Integer)
@@ -1667,6 +1693,11 @@
     }
 
     @DataClass.Generated.Member
+    public @Nullable LinkedHashSet<File> getOldPaths() {
+        return mOldPaths;
+    }
+
+    @DataClass.Generated.Member
     public float getLoadingProgress() {
         return mLoadingProgress;
     }
@@ -1748,10 +1779,10 @@
     }
 
     @DataClass.Generated(
-            time = 1700251133016L,
+            time = 1702666890740L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java",
-            inputSignatures = "private  int mBooleans\nprivate  int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate  int mAppId\nprivate @android.annotation.Nullable com.android.internal.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate  float mLoadingProgress\nprivate  long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate  long mLastModifiedTime\nprivate  long lastUpdateTime\nprivate  long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate  int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate  int mTargetSdkVersion\nprivate @android.annotation.Nullable byte[] mRestrictUpdateHash\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic  com.android.server.pm.PackageSetting snapshot()\npublic  void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic  com.android.server.pm.PackageSetting setAppId(int)\npublic  com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic  com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic  com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic  com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic  com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic  com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n  com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic  com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic  com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic  com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic  com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic  boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic  com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic  com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic  com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic  com.android.server.pm.PackageSetting setSharedUserAppId(int)\npublic  com.android.server.pm.PackageSetting setTargetSdkVersion(int)\npublic  com.android.server.pm.PackageSetting setRestrictUpdateHash(byte[])\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprivate  void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  void updateFrom(com.android.server.pm.PackageSetting)\n  com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic  com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic  boolean isPrivileged()\npublic  boolean isOem()\npublic  boolean isVendor()\npublic  boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic  boolean isSystemExt()\npublic  boolean isOdm()\npublic  boolean isSystem()\npublic  boolean isRequestLegacyExternalStorage()\npublic  boolean isUserDataFragile()\npublic  android.content.pm.SigningDetails getSigningDetails()\npublic  com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic  void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic  com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n  void setEnabled(int,int,java.lang.String)\n  int getEnabled(int)\n  void setInstalled(boolean,int)\n  boolean getInstalled(int)\n  int getInstallReason(int)\n  void setInstallReason(int,int)\n  int getUninstallReason(int)\n  void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n  boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n  boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n  int[] queryInstalledUsers(int[],boolean)\n  int[] queryUsersInstalledOrHasData(int[])\n  long getCeDataInode(int)\n  long getDeDataInode(int)\n  void setCeDataInode(long,int)\n  void setDeDataInode(long,int)\n  boolean getStopped(int)\n  void setStopped(boolean,int)\npublic  com.android.server.pm.PackageSetting setScannedAsStoppedSystemApp(boolean)\n  boolean getNotLaunched(int)\n  void setNotLaunched(boolean,int)\n  boolean getHidden(int)\n  void setHidden(boolean,int)\n  int getDistractionFlags(int)\n  void setDistractionFlags(int,int)\npublic  boolean getInstantApp(int)\n  void setInstantApp(boolean,int)\n  boolean getVirtualPreload(int)\n  void setVirtualPreload(boolean,int)\n  void setUserState(int,long,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n  void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n  void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n  void addDisabledComponent(java.lang.String,int)\n  void addEnabledComponent(java.lang.String,int)\n  boolean enableComponentLPw(java.lang.String,int)\n  boolean disableComponentLPw(java.lang.String,int)\n  boolean restoreComponentLPw(java.lang.String,int)\n  int getCurrentEnabledStateLPr(java.lang.String,int)\n  void removeUser(int)\npublic  int[] getNotInstalledUserIds()\n  void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected  void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static  void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\npublic @com.android.internal.annotations.VisibleForTesting com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic  void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic  boolean isIncremental()\npublic  boolean isLoading()\npublic  com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic  com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic  com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic  com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic  com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic  com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic  com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic  com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic  com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\npublic @java.lang.Override boolean isPersistent()\npublic @java.lang.Override boolean isScannedAsStoppedSystemApp()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final  int INSTALL_PERMISSION_FIXED\nprivate static final  int UPDATE_AVAILABLE\nprivate static final  int FORCE_QUERYABLE_OVERRIDE\nprivate static final  int SCANNED_AS_STOPPED_SYSTEM_APP\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
+            inputSignatures = "private  int mBooleans\nprivate  int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable boolean[] usesSdkLibrariesOptional\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate  int mAppId\nprivate @android.annotation.Nullable com.android.internal.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate @android.annotation.Nullable java.util.LinkedHashSet<java.io.File> mOldPaths\nprivate  float mLoadingProgress\nprivate  long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate  long mLastModifiedTime\nprivate  long lastUpdateTime\nprivate  long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate  int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate  int mTargetSdkVersion\nprivate @android.annotation.Nullable byte[] mRestrictUpdateHash\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic  com.android.server.pm.PackageSetting snapshot()\npublic  void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic  com.android.server.pm.PackageSetting setAppId(int)\npublic  com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic  com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic  com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic  com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic  com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic  com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n  com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic  com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic  com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic  com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic  com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic  boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic  com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic  com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic  com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic  com.android.server.pm.PackageSetting setSharedUserAppId(int)\npublic  com.android.server.pm.PackageSetting setTargetSdkVersion(int)\npublic  com.android.server.pm.PackageSetting setRestrictUpdateHash(byte[])\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprivate  void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  void updateFrom(com.android.server.pm.PackageSetting)\n  com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic  com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic  boolean isPrivileged()\npublic  boolean isOem()\npublic  boolean isVendor()\npublic  boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic  boolean isSystemExt()\npublic  boolean isOdm()\npublic  boolean isSystem()\npublic  boolean isRequestLegacyExternalStorage()\npublic  boolean isUserDataFragile()\npublic  android.content.pm.SigningDetails getSigningDetails()\npublic  com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic  void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic  com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n  void setEnabled(int,int,java.lang.String)\n  int getEnabled(int)\n  void setInstalled(boolean,int)\n  void setArchiveState(com.android.server.pm.pkg.ArchiveState,int)\n  boolean getInstalled(int)\n  boolean isArchived(int)\n  int getInstallReason(int)\n  void setInstallReason(int,int)\n  int getUninstallReason(int)\n  void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n  boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n  boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n  int[] queryInstalledUsers(int[],boolean)\n  int[] queryUsersInstalledOrHasData(int[])\n  long getCeDataInode(int)\n  long getDeDataInode(int)\n  void setCeDataInode(long,int)\n  void setDeDataInode(long,int)\n  boolean getStopped(int)\n  void setStopped(boolean,int)\npublic  com.android.server.pm.PackageSetting setScannedAsStoppedSystemApp(boolean)\n  boolean getNotLaunched(int)\n  void setNotLaunched(boolean,int)\n  boolean getHidden(int)\n  void setHidden(boolean,int)\n  int getDistractionFlags(int)\n  void setDistractionFlags(int,int)\npublic  boolean getInstantApp(int)\n  void setInstantApp(boolean,int)\n  boolean getVirtualPreload(int)\n  void setVirtualPreload(boolean,int)\n  void setUserState(int,long,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n  void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n  void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n  void addDisabledComponent(java.lang.String,int)\n  void addEnabledComponent(java.lang.String,int)\n  boolean enableComponentLPw(java.lang.String,int)\n  boolean disableComponentLPw(java.lang.String,int)\n  boolean restoreComponentLPw(java.lang.String,int)\n  int getCurrentEnabledStateLPr(java.lang.String,int)\n  void removeUser(int)\npublic  int[] getNotInstalledUserIds()\n  void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected  void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static  void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\npublic @com.android.internal.annotations.VisibleForTesting com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting com.android.server.pm.PackageSetting addOldPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting com.android.server.pm.PackageSetting removeOldPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic  void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic  boolean isIncremental()\npublic  boolean isLoading()\npublic  com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic  com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic  com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override boolean[] getUsesSdkLibrariesOptional()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic  com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic  com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic  com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic  com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic  com.android.server.pm.PackageSetting setUsesSdkLibrariesOptional(boolean[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic  com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic  com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\npublic @java.lang.Override boolean isPersistent()\npublic @java.lang.Override boolean isScannedAsStoppedSystemApp()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final  int INSTALL_PERMISSION_FIXED\nprivate static final  int UPDATE_AVAILABLE\nprivate static final  int FORCE_QUERYABLE_OVERRIDE\nprivate static final  int SCANNED_AS_STOPPED_SYSTEM_APP\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index f42b43f..2854453 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -30,6 +30,7 @@
 import static com.android.server.pm.PackageManagerService.TAG;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.pm.PackageManager;
 import android.content.pm.parsing.ApkLiteParseUtils;
 import android.content.pm.parsing.PackageLite;
@@ -456,15 +457,25 @@
         }
     }
 
-    void cleanUpResources(File codeFile, String[] instructionSets) {
+    void cleanUpResources(@Nullable String packageName, @Nullable File codeFile,
+                          @Nullable String[] instructionSets) {
         synchronized (mPm.mInstallLock) {
             cleanUpResourcesLI(codeFile, instructionSets);
         }
+        if (packageName == null) {
+            return;
+        }
+        synchronized (mPm.mLock) {
+            PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
+            if (ps != null) {
+                ps.removeOldPath(codeFile);
+            }
+        }
     }
 
     // Need installer lock especially for dex file removal.
     @GuardedBy("mPm.mInstallLock")
-    private void cleanUpResourcesLI(File codeFile, String[] instructionSets) {
+    private void cleanUpResourcesLI(@Nullable File codeFile, @Nullable String[] instructionSets) {
         // Try enumerating all code paths before deleting
         List<String> allCodePaths = Collections.EMPTY_LIST;
         if (codeFile != null && codeFile.exists()) {
@@ -482,7 +493,8 @@
     }
 
     @GuardedBy("mPm.mInstallLock")
-    private void removeDexFilesLI(List<String> allCodePaths, String[] instructionSets) {
+    private void removeDexFilesLI(@NonNull List<String> allCodePaths,
+                                  @Nullable  String[] instructionSets) {
         if (!allCodePaths.isEmpty()) {
             if (instructionSets == null) {
                 throw new IllegalStateException("instructionSet == null");
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 5c6d61e..2e67b2f 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -37,6 +37,7 @@
 import static com.android.server.pm.PackageManagerService.SCAN_AS_VENDOR;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_VIRTUAL_PRELOAD;
 import static com.android.server.pm.PackageManagerService.SCAN_BOOTING;
+import static com.android.server.pm.PackageManagerService.SCAN_DONT_KILL_APP;
 import static com.android.server.pm.PackageManagerService.SCAN_FIRST_BOOT_OR_UPGRADE;
 import static com.android.server.pm.PackageManagerService.SCAN_MOVE;
 import static com.android.server.pm.PackageManagerService.SCAN_NEW_INSTALL;
@@ -227,6 +228,7 @@
             // make a deep copy to avoid modifying any existing system state.
             pkgSetting = new PackageSetting(pkgSetting);
             pkgSetting.setPkg(parsedPackage);
+            final boolean isDontKill = (scanFlags & SCAN_DONT_KILL_APP) != 0;
 
             // REMOVE SharedUserSetting from method; update in a separate call.
             //
@@ -244,7 +246,8 @@
                     parsedPackage.getUsesSdkLibrariesOptional(),
                     usesStaticLibraries, parsedPackage.getUsesStaticLibrariesVersions(),
                     parsedPackage.getMimeGroups(), newDomainSetId,
-                    parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash());
+                    parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash(),
+                    isDontKill);
         }
 
         if (createNewPackage && originalPkgSetting != null) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 460bbb7..edae273 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -41,6 +41,7 @@
 import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.Flags;
 import android.content.pm.IntentFilterVerificationInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
@@ -51,6 +52,7 @@
 import android.content.pm.Signature;
 import android.content.pm.SuspendDialogInfo;
 import android.content.pm.UserInfo;
+import android.content.pm.UserPackage;
 import android.content.pm.VerifierDeviceIdentity;
 import android.content.pm.overlay.OverlayPaths;
 import android.net.Uri;
@@ -1227,9 +1229,10 @@
             @Nullable boolean[] usesSdkLibrariesOptional,
             @Nullable String[] usesStaticLibraries, @Nullable long[] usesStaticLibrariesVersions,
             @Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId,
-            int targetSdkVersion, byte[] restrictUpdatedHash)
+            int targetSdkVersion, byte[] restrictUpdatedHash, boolean isDontKill)
                     throws PackageManagerException {
         final String pkgName = pkgSetting.getPackageName();
+        final File oldCodePath = pkgSetting.getPath();
         if (sharedUser != null) {
             if (!Objects.equals(existingSharedUserSetting, sharedUser)) {
                 PackageManagerService.reportSettingsProblem(Log.WARN,
@@ -1246,7 +1249,7 @@
             pkgSetting.setSharedUserAppId(INVALID_UID);
         }
 
-        if (!pkgSetting.getPath().equals(codePath)) {
+        if (!oldCodePath.equals(codePath)) {
             final boolean isSystem = pkgSetting.isSystem();
             Slog.i(PackageManagerService.TAG,
                     "Update" + (isSystem ? " system" : "")
@@ -1274,6 +1277,11 @@
                 pkgSetting.setLegacyNativeLibraryPath(legacyNativeLibraryPath);
             }
             pkgSetting.setPath(codePath);
+            if (isDontKill && Flags.improveInstallDontKill()) {
+                // We retain old code paths for DONT_KILL installs. Keep a record of old paths until
+                // they are removed.
+                pkgSetting.addOldPath(oldCodePath);
+            }
         }
 
         pkgSetting.setPrimaryCpuAbi(primaryCpuAbi)
@@ -1956,7 +1964,7 @@
                         ArchiveState archiveState = null;
 
                         int packageDepth = parser.getDepth();
-                        ArrayMap<String, SuspendParams> suspendParamsMap = null;
+                        ArrayMap<UserPackage, SuspendParams> suspendParamsMap = null;
                         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                                 && (type != XmlPullParser.END_TAG
                                 || parser.getDepth() > packageDepth)) {
@@ -1983,18 +1991,15 @@
                                             parser);
                                     break;
                                 case TAG_SUSPEND_PARAMS:
-                                    final String suspendingPackage = parser.getAttributeValue(null,
-                                            ATTR_SUSPENDING_PACKAGE);
-                                    if (suspendingPackage == null) {
-                                        Slog.wtf(TAG, "No suspendingPackage found inside tag "
-                                                + TAG_SUSPEND_PARAMS);
+                                    Map.Entry<UserPackage, SuspendParams> entry =
+                                            readSuspensionParamsLPr(userId, parser);
+                                    if (entry == null) {
                                         continue;
                                     }
                                     if (suspendParamsMap == null) {
                                         suspendParamsMap = new ArrayMap<>();
                                     }
-                                    suspendParamsMap.put(suspendingPackage,
-                                            SuspendParams.restoreFromXml(parser));
+                                    suspendParamsMap.put(entry.getKey(), entry.getValue());
                                     break;
                                 case TAG_ARCHIVE_STATE:
                                     archiveState = parseArchiveState(parser);
@@ -2016,7 +2021,8 @@
                                     oldSuspendedLauncherExtras,
                                     false /* quarantined */);
                             suspendParamsMap = new ArrayMap<>();
-                            suspendParamsMap.put(oldSuspendingPackage, suspendParams);
+                            suspendParamsMap.put(
+                                    UserPackage.of(userId, oldSuspendingPackage), suspendParams);
                         }
 
                         if (blockUninstall) {
@@ -2058,6 +2064,20 @@
         }
     }
 
+    @Nullable
+    private static Map.Entry<UserPackage, SuspendParams> readSuspensionParamsLPr(
+            int userId, TypedXmlPullParser parser) throws IOException {
+        final String suspendingPackage = parser.getAttributeValue(null, ATTR_SUSPENDING_PACKAGE);
+        if (suspendingPackage == null) {
+            Slog.wtf(TAG, "No suspendingPackage found inside tag " + TAG_SUSPEND_PARAMS);
+            return null;
+        }
+        final int suspendingUserId = userId;
+        return Map.entry(
+                UserPackage.of(suspendingUserId, suspendingPackage),
+                SuspendParams.restoreFromXml(parser));
+    }
+
     private static ArchiveState parseArchiveState(TypedXmlPullParser parser)
             throws XmlPullParserException, IOException {
         String installerTitle = parser.getAttributeValue(null,
@@ -2414,10 +2434,11 @@
                         }
                         if (ustate.isSuspended()) {
                             for (int i = 0; i < ustate.getSuspendParams().size(); i++) {
-                                final String suspendingPackage = ustate.getSuspendParams().keyAt(i);
+                                final UserPackage suspendingPackage =
+                                        ustate.getSuspendParams().keyAt(i);
                                 serializer.startTag(null, TAG_SUSPEND_PARAMS);
                                 serializer.attribute(null, ATTR_SUSPENDING_PACKAGE,
-                                        suspendingPackage);
+                                        suspendingPackage.packageName);
                                 final SuspendParams params =
                                         ustate.getSuspendParams().valueAt(i);
                                 if (params != null) {
@@ -4955,6 +4976,11 @@
         }
         pw.print(prefix); pw.print("  pkg="); pw.println(pkg);
         pw.print(prefix); pw.print("  codePath="); pw.println(ps.getPathString());
+        if (ps.getOldPaths() != null && ps.getOldPaths().size() > 0) {
+            for (File oldPath : ps.getOldPaths()) {
+                pw.print(prefix); pw.println("    oldCodePath=" + oldPath.getAbsolutePath());
+            }
+        }
         if (permissionNames == null) {
             pw.print(prefix); pw.print("  resourcePath="); pw.println(ps.getPathString());
             pw.print(prefix); pw.print("  legacyNativeLibraryDir=");
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index c2a960a..4e70cc5 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -27,10 +27,10 @@
 import android.app.AppOpsManager;
 import android.content.Intent;
 import android.content.pm.SuspendDialogInfo;
+import android.content.pm.UserPackage;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.PersistableBundle;
-import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.ArrayMap;
@@ -88,8 +88,8 @@
      * @param dialogInfo An optional {@link SuspendDialogInfo} object describing the dialog that
      *                   should be shown to the user when they try to launch a suspended app.
      *                   Ignored if {@code suspended} is false.
-     * @param callingPackage The caller's package name.
-     * @param userId The user where packages reside.
+     * @param suspendingPackage The caller's package name.
+     * @param targetUserId The user where packages reside.
      * @param callingUid The caller's uid.
      * @return The names of failed packages.
      */
@@ -97,14 +97,14 @@
     String[] setPackagesSuspended(@NonNull Computer snapshot, @Nullable String[] packageNames,
             boolean suspended, @Nullable PersistableBundle appExtras,
             @Nullable PersistableBundle launcherExtras, @Nullable SuspendDialogInfo dialogInfo,
-            @NonNull String callingPackage, @UserIdInt int userId, int callingUid,
+            @NonNull UserPackage suspendingPackage, @UserIdInt int targetUserId, int callingUid,
             boolean quarantined) {
         if (ArrayUtils.isEmpty(packageNames)) {
             return packageNames;
         }
-        if (suspended && !quarantined && !isSuspendAllowedForUser(snapshot, userId,
-                callingUid)) {
-            Slog.w(TAG, "Cannot suspend due to restrictions on user " + userId);
+        if (suspended && !quarantined
+                && !isSuspendAllowedForUser(snapshot, targetUserId, callingUid)) {
+            Slog.w(TAG, "Cannot suspend due to restrictions on user " + targetUserId);
             return packageNames;
         }
 
@@ -119,19 +119,21 @@
         final IntArray changedUids = new IntArray(packageNames.length);
 
         final boolean[] canSuspend = suspended
-                ? canSuspendPackageForUser(snapshot, packageNames, userId, callingUid)
+                ? canSuspendPackageForUser(snapshot, packageNames, targetUserId, callingUid)
                 : null;
         for (int i = 0; i < packageNames.length; i++) {
             final String packageName = packageNames[i];
-            if (callingPackage.equals(packageName)) {
-                Slog.w(TAG, "Calling package: " + callingPackage + " trying to "
+            if (suspendingPackage.packageName.equals(packageName)
+                    && suspendingPackage.userId == targetUserId) {
+                Slog.w(TAG, "Suspending package: " + suspendingPackage + " trying to "
                         + (suspended ? "" : "un") + "suspend itself. Ignoring");
                 unmodifiablePackages.add(packageName);
                 continue;
             }
             final PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName);
-            if (packageState == null || !packageState.getUserStateOrDefault(userId).isInstalled()
-                    || snapshot.shouldFilterApplication(packageState, callingUid, userId)) {
+            if (packageState == null
+                    || !packageState.getUserStateOrDefault(targetUserId).isInstalled()
+                    || snapshot.shouldFilterApplication(packageState, callingUid, targetUserId)) {
                 Slog.w(TAG, "Could not find package setting for package: " + packageName
                         + ". Skipping suspending/un-suspending.");
                 unmodifiablePackages.add(packageName);
@@ -142,34 +144,34 @@
                 continue;
             }
 
-            final WatchedArrayMap<String, SuspendParams> suspendParamsMap =
-                    packageState.getUserStateOrDefault(userId).getSuspendParams();
+            final WatchedArrayMap<UserPackage, SuspendParams> suspendParamsMap =
+                    packageState.getUserStateOrDefault(targetUserId).getSuspendParams();
             final SuspendParams oldSuspendParams = suspendParamsMap == null
-                    ? null : suspendParamsMap.get(callingPackage);
+                    ? null : suspendParamsMap.get(suspendingPackage);
             boolean changed = !Objects.equals(oldSuspendParams, newSuspendParams);
 
             if (suspended && !changed) {
                 // Carried over API behavior, must notify change even if no change
                 notifyPackagesList.add(packageName);
                 notifyUids.add(
-                        UserHandle.getUid(userId, packageState.getAppId()));
+                        UserHandle.getUid(targetUserId, packageState.getAppId()));
                 continue;
             }
 
-            // If only the callingPackage is suspending this package,
+            // If only the suspendingPackage is suspending this package,
             // it will be unsuspended when this change is committed
             boolean packageUnsuspended = !suspended
                     && CollectionUtils.size(suspendParamsMap) == 1
-                    && suspendParamsMap.containsKey(callingPackage);
+                    && suspendParamsMap.containsKey(suspendingPackage);
             if (suspended || packageUnsuspended) {
                 // Always notify of a suspend call + notify when fully unsuspended
                 notifyPackagesList.add(packageName);
-                notifyUids.add(UserHandle.getUid(userId, packageState.getAppId()));
+                notifyUids.add(UserHandle.getUid(targetUserId, packageState.getAppId()));
             }
 
             if (changed) {
                 changedPackagesList.add(packageName);
-                changedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
+                changedUids.add(UserHandle.getUid(targetUserId, packageState.getAppId()));
             } else {
                 Slog.w(TAG, "No change is needed for package: " + packageName
                         + ". Skipping suspending/un-suspending.");
@@ -181,11 +183,11 @@
             for (int index = 0; index < size; index++) {
                 final String packageName  = changedPackagesList.valueAt(index);
                 final PackageUserStateWrite userState = mutator.forPackage(packageName)
-                        .userState(userId);
+                        .userState(targetUserId);
                 if (suspended) {
-                    userState.putSuspendParams(callingPackage, newSuspendParams);
+                    userState.putSuspendParams(suspendingPackage, newSuspendParams);
                 } else {
-                    userState.removeSuspension(callingPackage);
+                    userState.removeSuspension(suspendingPackage);
                 }
             }
         });
@@ -197,17 +199,17 @@
             mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(newSnapshot,
                     suspended ? Intent.ACTION_PACKAGES_SUSPENDED
                             : Intent.ACTION_PACKAGES_UNSUSPENDED,
-                    changedPackages, notifyUids.toArray(), quarantined, userId);
+                    changedPackages, notifyUids.toArray(), quarantined, targetUserId);
             mBroadcastHelper.sendMyPackageSuspendedOrUnsuspended(newSnapshot, changedPackages,
-                    suspended, userId);
-            mPm.scheduleWritePackageRestrictions(userId);
+                    suspended, targetUserId);
+            mPm.scheduleWritePackageRestrictions(targetUserId);
         }
         // Send the suspension changed broadcast to ensure suspension state is not stale.
         if (!changedPackagesList.isEmpty()) {
             mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(newSnapshot,
                     Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
                     changedPackagesList.toArray(new String[0]), changedUids.toArray(), quarantined,
-                    userId);
+                    targetUserId);
         }
         return unmodifiablePackages.toArray(new String[0]);
     }
@@ -216,19 +218,19 @@
      * Returns the names in the {@code packageNames} which can not be suspended by the caller.
      *
      * @param packageNames The names of packages to check.
-     * @param userId The user where packages reside.
+     * @param targetUserId The user where packages reside.
      * @param callingUid The caller's uid.
      * @return The names of packages which are Unsuspendable.
      */
     @NonNull
     String[] getUnsuspendablePackagesForUser(@NonNull Computer snapshot,
-            @NonNull String[] packageNames, @UserIdInt int userId, int callingUid) {
-        if (!isSuspendAllowedForUser(snapshot, userId, callingUid)) {
-            Slog.w(TAG, "Cannot suspend due to restrictions on user " + userId);
+            @NonNull String[] packageNames, @UserIdInt int targetUserId, int callingUid) {
+        if (!isSuspendAllowedForUser(snapshot, targetUserId, callingUid)) {
+            Slog.w(TAG, "Cannot suspend due to restrictions on user " + targetUserId);
             return packageNames;
         }
         final ArraySet<String> unactionablePackages = new ArraySet<>();
-        final boolean[] canSuspend = canSuspendPackageForUser(snapshot, packageNames, userId,
+        final boolean[] canSuspend = canSuspendPackageForUser(snapshot, packageNames, targetUserId,
                 callingUid);
         for (int i = 0; i < packageNames.length; i++) {
             if (!canSuspend[i]) {
@@ -237,7 +239,7 @@
             }
             final PackageStateInternal packageState =
                     snapshot.getPackageStateForInstalledAndFiltered(
-                            packageNames[i], callingUid, userId);
+                            packageNames[i], callingUid, targetUserId);
             if (packageState == null) {
                 Slog.w(TAG, "Could not find package setting for package: " + packageNames[i]);
                 unactionablePackages.add(packageNames[i]);
@@ -285,30 +287,31 @@
      * @param packagesToChange The packages on which the suspension are to be removed.
      * @param suspendingPackagePredicate A predicate identifying the suspending packages whose
      *                                   suspensions will be removed.
-     * @param userId The user for which the changes are taking place.
+     * @param targetUserId The user for which the changes are taking place.
      */
     void removeSuspensionsBySuspendingPackage(@NonNull Computer snapshot,
             @NonNull String[] packagesToChange,
-            @NonNull Predicate<String> suspendingPackagePredicate, int userId) {
+            @NonNull Predicate<UserPackage> suspendingPackagePredicate, int targetUserId) {
         final List<String> unsuspendedPackages = new ArrayList<>();
         final IntArray unsuspendedUids = new IntArray();
-        final ArrayMap<String, ArraySet<String>> pkgToSuspendingPkgsToCommit = new ArrayMap<>();
+        final ArrayMap<String, ArraySet<UserPackage>> pkgToSuspendingPkgsToCommit =
+                new ArrayMap<>();
         for (String packageName : packagesToChange) {
             final PackageStateInternal packageState =
                     snapshot.getPackageStateInternal(packageName);
             final PackageUserStateInternal packageUserState = packageState == null
-                    ? null : packageState.getUserStateOrDefault(userId);
+                    ? null : packageState.getUserStateOrDefault(targetUserId);
             if (packageUserState == null || !packageUserState.isSuspended()) {
                 continue;
             }
 
-            WatchedArrayMap<String, SuspendParams> suspendParamsMap =
+            WatchedArrayMap<UserPackage, SuspendParams> suspendParamsMap =
                     packageUserState.getSuspendParams();
             int countRemoved = 0;
             for (int index = 0; index < suspendParamsMap.size(); index++) {
-                String suspendingPackage = suspendParamsMap.keyAt(index);
+                UserPackage suspendingPackage = suspendParamsMap.keyAt(index);
                 if (suspendingPackagePredicate.test(suspendingPackage)) {
-                    ArraySet<String> suspendingPkgsToCommit =
+                    ArraySet<UserPackage> suspendingPkgsToCommit =
                             pkgToSuspendingPkgsToCommit.get(packageName);
                     if (suspendingPkgsToCommit == null) {
                         suspendingPkgsToCommit = new ArraySet<>();
@@ -322,31 +325,33 @@
             // Everything would be removed and package unsuspended
             if (countRemoved == suspendParamsMap.size()) {
                 unsuspendedPackages.add(packageState.getPackageName());
-                unsuspendedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
+                unsuspendedUids.add(UserHandle.getUid(targetUserId, packageState.getAppId()));
             }
         }
 
         mPm.commitPackageStateMutation(null, mutator -> {
             for (int mapIndex = 0; mapIndex < pkgToSuspendingPkgsToCommit.size(); mapIndex++) {
                 String packageName = pkgToSuspendingPkgsToCommit.keyAt(mapIndex);
-                ArraySet<String> packagesToRemove = pkgToSuspendingPkgsToCommit.valueAt(mapIndex);
-                PackageUserStateWrite userState = mutator.forPackage(packageName).userState(userId);
+                ArraySet<UserPackage> packagesToRemove =
+                        pkgToSuspendingPkgsToCommit.valueAt(mapIndex);
+                PackageUserStateWrite userState =
+                        mutator.forPackage(packageName).userState(targetUserId);
                 for (int setIndex = 0; setIndex < packagesToRemove.size(); setIndex++) {
                     userState.removeSuspension(packagesToRemove.valueAt(setIndex));
                 }
             }
         });
 
-        mPm.scheduleWritePackageRestrictions(userId);
+        mPm.scheduleWritePackageRestrictions(targetUserId);
         final Computer newSnapshot = mPm.snapshotComputer();
         if (!unsuspendedPackages.isEmpty()) {
             final String[] packageArray = unsuspendedPackages.toArray(
                     new String[unsuspendedPackages.size()]);
             mBroadcastHelper.sendMyPackageSuspendedOrUnsuspended(newSnapshot, packageArray,
-                    false, userId);
+                    false, targetUserId);
             mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(newSnapshot,
                     Intent.ACTION_PACKAGES_UNSUSPENDED,
-                    packageArray, unsuspendedUids.toArray(), false, userId);
+                    packageArray, unsuspendedUids.toArray(), false, targetUserId);
         }
     }
 
@@ -404,7 +409,7 @@
      * @return The name of suspending package.
      */
     @Nullable
-    String getSuspendingPackage(@NonNull Computer snapshot, @NonNull String suspendedPackage,
+    UserPackage getSuspendingPackage(@NonNull Computer snapshot, @NonNull String suspendedPackage,
             int userId, int callingUid) {
         final PackageStateInternal packageState = snapshot.getPackageStateInternal(
                 suspendedPackage, callingUid);
@@ -417,13 +422,13 @@
             return null;
         }
 
-        String suspendingPackage = null;
-        String suspendedBySystem = null;
-        String qasPackage = null;
+        UserPackage suspendingPackage = null;
+        UserPackage suspendedBySystem = null;
+        UserPackage qasPackage = null;
         for (int i = 0; i < userState.getSuspendParams().size(); i++) {
             suspendingPackage = userState.getSuspendParams().keyAt(i);
             var suspendParams = userState.getSuspendParams().valueAt(i);
-            if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) {
+            if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage.packageName)) {
                 suspendedBySystem = suspendingPackage;
             }
             if (suspendParams.isQuarantined() && qasPackage == null) {
@@ -451,7 +456,7 @@
      */
     @Nullable
     SuspendDialogInfo getSuspendedDialogInfo(@NonNull Computer snapshot,
-            @NonNull String suspendedPackage, @NonNull String suspendingPackage, int userId,
+            @NonNull String suspendedPackage, @NonNull UserPackage suspendingPackage, int userId,
             int callingUid) {
         final PackageStateInternal packageState = snapshot.getPackageStateInternal(
                 suspendedPackage, callingUid);
@@ -464,7 +469,7 @@
             return null;
         }
 
-        final WatchedArrayMap<String, SuspendParams> suspendParamsMap =
+        final WatchedArrayMap<UserPackage, SuspendParams> suspendParamsMap =
                 userState.getSuspendParams();
         if (suspendParamsMap == null) {
             return null;
@@ -493,34 +498,36 @@
      * be suspended or not.
      *
      * @param packageNames  The package names to check suspendability for.
-     * @param userId The user to check in
+     * @param targetUserId The user to check in
      * @param callingUid The caller's uid.
      * @return An array containing results of the checks
      */
     @NonNull
     boolean[] canSuspendPackageForUser(@NonNull Computer snapshot, @NonNull String[] packageNames,
-            int userId, int callingUid) {
+            int targetUserId, int callingUid) {
         final boolean[] canSuspend = new boolean[packageNames.length];
-        final boolean isCallerOwner = isCallerDeviceOrProfileOwner(snapshot, userId, callingUid);
+        final boolean isCallerOwner =
+                isCallerDeviceOrProfileOwner(snapshot, targetUserId, callingUid);
         final long token = Binder.clearCallingIdentity();
         try {
             final DefaultAppProvider defaultAppProvider = mInjector.getDefaultAppProvider();
-            final String activeLauncherPackageName = defaultAppProvider.getDefaultHome(userId);
-            final String dialerPackageName = defaultAppProvider.getDefaultDialer(userId);
+            final String activeLauncherPackageName =
+                    defaultAppProvider.getDefaultHome(targetUserId);
+            final String dialerPackageName = defaultAppProvider.getDefaultDialer(targetUserId);
             final String requiredInstallerPackage =
-                    getKnownPackageName(snapshot, KnownPackages.PACKAGE_INSTALLER, userId);
+                    getKnownPackageName(snapshot, KnownPackages.PACKAGE_INSTALLER, targetUserId);
             final String requiredUninstallerPackage =
-                    getKnownPackageName(snapshot, KnownPackages.PACKAGE_UNINSTALLER, userId);
+                    getKnownPackageName(snapshot, KnownPackages.PACKAGE_UNINSTALLER, targetUserId);
             final String requiredVerifierPackage =
-                    getKnownPackageName(snapshot, KnownPackages.PACKAGE_VERIFIER, userId);
+                    getKnownPackageName(snapshot, KnownPackages.PACKAGE_VERIFIER, targetUserId);
             final String requiredPermissionControllerPackage =
                     getKnownPackageName(snapshot, KnownPackages.PACKAGE_PERMISSION_CONTROLLER,
-                            userId);
+                            targetUserId);
             for (int i = 0; i < packageNames.length; i++) {
                 canSuspend[i] = false;
                 final String packageName = packageNames[i];
 
-                if (mPm.isPackageDeviceAdmin(packageName, userId)) {
+                if (mPm.isPackageDeviceAdmin(packageName, targetUserId)) {
                     Slog.w(TAG, "Cannot suspend package \"" + packageName
                             + "\": has an active device admin");
                     continue;
@@ -555,12 +562,12 @@
                             + "\": required for permissions management");
                     continue;
                 }
-                if (mProtectedPackages.isPackageStateProtected(userId, packageName)) {
+                if (mProtectedPackages.isPackageStateProtected(targetUserId, packageName)) {
                     Slog.w(TAG, "Cannot suspend package \"" + packageName
                             + "\": protected package");
                     continue;
                 }
-                if (!isCallerOwner && snapshot.getBlockUninstall(userId, packageName)) {
+                if (!isCallerOwner && snapshot.getBlockUninstall(targetUserId, packageName)) {
                     Slog.w(TAG, "Cannot suspend package \"" + packageName
                             + "\": blocked by admin");
                     continue;
@@ -572,7 +579,7 @@
                 PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName);
                 AndroidPackage pkg = packageState == null ? null : packageState.getPkg();
                 if (pkg != null) {
-                    final int uid = UserHandle.getUid(userId, packageState.getAppId());
+                    final int uid = UserHandle.getUid(targetUserId, packageState.getAppId());
                     // Cannot suspend SDK libs as they are controlled by SDK manager.
                     if (pkg.isSdkLibrary()) {
                         Slog.w(TAG, "Cannot suspend package: " + packageName
@@ -614,20 +621,6 @@
                         == AppOpsManager.MODE_ALLOWED;
     }
 
-    /**
-     * Suspends packages on behalf of an admin.
-     *
-     * @return array of packages that are unsuspendable, either because admin is not allowed to
-     * suspend them (e.g. current dialer) or there was other problem (e.g. package not found).
-     */
-    public String[] setPackagesSuspendedByAdmin(
-            Computer snapshot, int userId, String[] packageNames, boolean suspend) {
-        return setPackagesSuspended(snapshot, packageNames, suspend,
-                null /* appExtras */, null /* launcherExtras */, null /* dialogInfo */,
-                PackageManagerService.PLATFORM_PACKAGE_NAME, userId, Process.SYSTEM_UID,
-                false /* quarantined */);
-    }
-
     private String getKnownPackageName(@NonNull Computer snapshot,
             @KnownPackages.KnownPackage int knownPackage, int userId) {
         final String[] knownPackages =
@@ -635,14 +628,15 @@
         return knownPackages.length > 0 ? knownPackages[0] : null;
     }
 
-    private boolean isCallerDeviceOrProfileOwner(@NonNull Computer snapshot, int userId,
+    private boolean isCallerDeviceOrProfileOwner(@NonNull Computer snapshot, int targetUserId,
             int callingUid) {
         if (callingUid == SYSTEM_UID) {
             return true;
         }
-        final String ownerPackage = mProtectedPackages.getDeviceOwnerOrProfileOwnerPackage(userId);
+        final String ownerPackage =
+                mProtectedPackages.getDeviceOwnerOrProfileOwnerPackage(targetUserId);
         if (ownerPackage != null) {
-            return callingUid == snapshot.getPackageUidInternal(ownerPackage, 0, userId,
+            return callingUid == snapshot.getPackageUidInternal(ownerPackage, 0, targetUserId,
                     callingUid);
         }
         return false;
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index c48eccf..2305d6c 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -7237,7 +7237,8 @@
 
         @Override
         public boolean isUserInitialized(@UserIdInt int userId) {
-            return (getUserInfo(userId).flags & UserInfo.FLAG_INITIALIZED) != 0;
+            final UserInfo userInfo = getUserInfo(userId);
+            return userInfo != null && (userInfo.flags & UserInfo.FLAG_INITIALIZED) != 0;
         }
 
         @Override
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 671e031..3afba39 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -3291,7 +3291,7 @@
         final PermissionAllowlist permissionAllowlist =
                 SystemConfig.getInstance().getPermissionAllowlist();
         final String packageName = packageState.getPackageName();
-        if (packageState.isVendor()) {
+        if (packageState.isVendor() || packageState.isOdm()) {
             return permissionAllowlist.getVendorPrivilegedAppAllowlistState(packageName,
                     permissionName);
         } else if (packageState.isProduct()) {
@@ -3386,7 +3386,7 @@
             // the permission's protectionLevel does not have the extra 'vendorPrivileged'
             // flag.
             if (allowed && isPrivilegedPermission && !bp.isVendorPrivileged()
-                    && pkgSetting.isVendor()) {
+                    && (pkgSetting.isVendor() || pkgSetting.isOdm())) {
                 Slog.w(TAG, "Permission " + permissionName
                         + " cannot be granted to privileged vendor apk " + pkg.getPackageName()
                         + " because it isn't a 'vendorPrivileged' permission.");
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java
index 2f4ad2d8..15b693c 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.pm.PackageManager;
+import android.content.pm.UserPackage;
 import android.content.pm.overlay.OverlayPaths;
 import android.util.ArraySet;
 import android.util.Pair;
@@ -173,7 +174,7 @@
 
     @Nullable
     @Override
-    public WatchedArrayMap<String, SuspendParams> getSuspendParams() {
+    public WatchedArrayMap<UserPackage, SuspendParams> getSuspendParams() {
         return null;
     }
 
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
index c5ef525..7a5a14d 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
@@ -22,6 +22,7 @@
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.pm.PackageManager;
+import android.content.pm.UserPackage;
 import android.content.pm.overlay.OverlayPaths;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -121,7 +122,7 @@
      * Suspending package to suspend params
      */
     @Nullable
-    private WatchedArrayMap<String, SuspendParams> mSuspendParams;
+    private WatchedArrayMap<UserPackage, SuspendParams> mSuspendParams;
 
     @Nullable
     private WatchedArrayMap<ComponentName, Pair<String, Integer>> mComponentLabelIconOverrideMap;
@@ -369,7 +370,10 @@
         return !CollectionUtils.isEmpty(mSuspendParams);
     }
 
-    public PackageUserStateImpl putSuspendParams(@NonNull String suspendingPackage,
+    /**
+     * Adds or updates suspension params by the given package.
+     */
+    public PackageUserStateImpl putSuspendParams(@NonNull UserPackage suspendingPackage,
             @Nullable SuspendParams suspendParams) {
         if (mSuspendParams == null) {
             mSuspendParams = new WatchedArrayMap<>();
@@ -384,7 +388,10 @@
         return this;
     }
 
-    public PackageUserStateImpl removeSuspension(@NonNull String suspendingPackage) {
+    /**
+     * Removes suspension by the given package.
+     */
+    public PackageUserStateImpl removeSuspension(@NonNull UserPackage suspendingPackage) {
         if (mSuspendParams != null) {
             mSuspendParams.remove(suspendingPackage);
             onChanged();
@@ -565,7 +572,7 @@
      * Suspending package to suspend params
      */
     public @NonNull PackageUserStateImpl setSuspendParams(
-            @NonNull ArrayMap<String, SuspendParams> value) {
+            @NonNull ArrayMap<UserPackage, SuspendParams> value) {
         if (value == null) {
             return this;
         }
@@ -778,7 +785,7 @@
      * Suspending package to suspend params
      */
     @DataClass.Generated.Member
-    public @Nullable WatchedArrayMap<String,SuspendParams> getSuspendParams() {
+    public @Nullable WatchedArrayMap<UserPackage,SuspendParams> getSuspendParams() {
         return mSuspendParams;
     }
 
@@ -830,7 +837,7 @@
      * Suspending package to suspend params
      */
     @DataClass.Generated.Member
-    public @NonNull PackageUserStateImpl setSuspendParams(@NonNull WatchedArrayMap<String,SuspendParams> value) {
+    public @NonNull PackageUserStateImpl setSuspendParams(@NonNull WatchedArrayMap<UserPackage,SuspendParams> value) {
         mSuspendParams = value;
         return this;
     }
@@ -909,10 +916,10 @@
     }
 
     @DataClass.Generated(
-            time = 1701470095849L,
+            time = 1701864813354L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java",
-            inputSignatures = "private  int mBooleans\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate  long mCeDataInode\nprivate  long mDeDataInode\nprivate  int mDistractionFlags\nprivate @android.content.pm.PackageManager.EnabledState int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate @android.annotation.CurrentTimeMillisLong long mFirstInstallTimeMillis\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nprivate @android.annotation.Nullable com.android.server.pm.pkg.ArchiveState mArchiveState\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate  void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic  boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic  void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic  com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic  com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setMinAspectRatio(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTimeMillis(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setArchiveState(com.android.server.pm.pkg.ArchiveState)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate  boolean watchableEquals(com.android.server.utils.Watchable)\nprivate  int watchableHashCode()\nprivate  boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate  int snapshotHashCode()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isQuarantined()\npublic @java.lang.Override boolean dataExists()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\nprivate static final  int INSTALLED\nprivate static final  int STOPPED\nprivate static final  int NOT_LAUNCHED\nprivate static final  int HIDDEN\nprivate static final  int INSTANT_APP\nprivate static final  int VIRTUAL_PRELOADED\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)")
+            inputSignatures = "private  int mBooleans\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate  long mCeDataInode\nprivate  long mDeDataInode\nprivate  int mDistractionFlags\nprivate @android.content.pm.PackageManager.EnabledState int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.pm.UserPackage,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate @android.annotation.CurrentTimeMillisLong long mFirstInstallTimeMillis\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nprivate @android.annotation.Nullable com.android.server.pm.pkg.ArchiveState mArchiveState\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate  void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic  boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic  void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic  com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(android.content.pm.UserPackage,com.android.server.pm.pkg.SuspendParams)\npublic  com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(android.content.pm.UserPackage)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setMinAspectRatio(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<android.content.pm.UserPackage,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTimeMillis(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setArchiveState(com.android.server.pm.pkg.ArchiveState)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate  boolean watchableEquals(com.android.server.utils.Watchable)\nprivate  int watchableHashCode()\nprivate  boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate  int snapshotHashCode()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isQuarantined()\npublic @java.lang.Override boolean dataExists()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\nprivate static final  int INSTALLED\nprivate static final  int STOPPED\nprivate static final  int NOT_LAUNCHED\nprivate static final  int HIDDEN\nprivate static final  int INSTANT_APP\nprivate static final  int VIRTUAL_PRELOADED\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java
index 46cc830..f8d745c 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ComponentName;
+import android.content.pm.UserPackage;
 import android.content.pm.pkg.FrameworkPackageUserState;
 import android.util.Pair;
 
@@ -38,7 +39,7 @@
 
     // TODO: Make non-null with emptyMap()
     @Nullable
-    WatchedArrayMap<String, SuspendParams> getSuspendParams();
+    WatchedArrayMap<UserPackage, SuspendParams> getSuspendParams();
 
     @Nullable
     WatchedArraySet<String> getDisabledComponentsNoCopy();
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
index 8430cf7..253eb40 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
@@ -21,6 +21,7 @@
 import android.content.ComponentName;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.UserPackage;
 import android.content.pm.overlay.OverlayPaths;
 import android.util.ArraySet;
 
@@ -349,7 +350,7 @@
 
             @NonNull
             @Override
-            public PackageUserStateWrite putSuspendParams(@NonNull String suspendingPackage,
+            public PackageUserStateWrite putSuspendParams(@NonNull UserPackage suspendingPackage,
                     @Nullable SuspendParams suspendParams) {
                 if (mUserState != null) {
                     mUserState.putSuspendParams(suspendingPackage, suspendParams);
@@ -359,7 +360,7 @@
 
             @NonNull
             @Override
-            public PackageUserStateWrite removeSuspension(@NonNull String suspendingPackage) {
+            public PackageUserStateWrite removeSuspension(@NonNull UserPackage suspendingPackage) {
                 if (mUserState != null) {
                     mUserState.removeSuspension(suspendingPackage);
                 }
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java
index 0c6c672..f6b2104 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.pm.PackageManager;
+import android.content.pm.UserPackage;
 import android.content.pm.overlay.OverlayPaths;
 
 import com.android.server.pm.pkg.PackageUserStateImpl;
@@ -38,11 +39,11 @@
             @PackageManager.DistractionRestriction int restrictionFlags);
 
     @NonNull
-    PackageUserStateWrite putSuspendParams(@NonNull String suspendingPackage,
+    PackageUserStateWrite putSuspendParams(@NonNull UserPackage suspendingPackage,
             @Nullable SuspendParams suspendParams);
 
     @NonNull
-    PackageUserStateWrite removeSuspension(@NonNull String suspendingPackage);
+    PackageUserStateWrite removeSuspension(@NonNull UserPackage suspendingPackage);
 
     @NonNull
     PackageUserStateWrite setHidden(boolean hidden);
diff --git a/services/core/java/com/android/server/utils/WatchedSparseSetArray.java b/services/core/java/com/android/server/utils/WatchedSparseSetArray.java
index b8850af..ff426cc 100644
--- a/services/core/java/com/android/server/utils/WatchedSparseSetArray.java
+++ b/services/core/java/com/android/server/utils/WatchedSparseSetArray.java
@@ -47,6 +47,17 @@
     }
 
     /**
+     * Create a new WatchedSparseSetArray from an existing SparseSetArray without copying.
+     * <p>
+     * Use with caution: Callers must ensure that no reference to {@code sparseSetArray} exists
+     * anywhere else in the system. If such a reference does exist, then changes to the storage via
+     * that reference will not be noticed by the new WatchedSpareSetArray.
+     */
+    public WatchedSparseSetArray(@NonNull SparseSetArray<T> sparseSetArray) {
+        mStorage = sparseSetArray;
+    }
+
+    /**
      * Return the underlying storage.  This breaks the wrapper but is necessary when
      * passing the array to distant methods.
      */
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 2df965c..1ce87a7 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -182,6 +182,7 @@
 import static com.android.server.wm.ActivityRecordProto.REPORTED_DRAWN;
 import static com.android.server.wm.ActivityRecordProto.REPORTED_VISIBLE;
 import static com.android.server.wm.ActivityRecordProto.SHOULD_FORCE_ROTATE_FOR_CAMERA_COMPAT;
+import static com.android.server.wm.ActivityRecordProto.SHOULD_OVERRIDE_MIN_ASPECT_RATIO;
 import static com.android.server.wm.ActivityRecordProto.SHOULD_REFRESH_ACTIVITY_FOR_CAMERA_COMPAT;
 import static com.android.server.wm.ActivityRecordProto.SHOULD_REFRESH_ACTIVITY_VIA_PAUSE_FOR_CAMERA_COMPAT;
 import static com.android.server.wm.ActivityRecordProto.SHOULD_SEND_COMPAT_FAKE_FOCUS;
@@ -564,7 +565,6 @@
     boolean idle;           // has the activity gone idle?
     boolean hasBeenLaunched;// has this activity ever been launched?
     boolean immersive;      // immersive mode (don't interrupt if possible)
-    boolean forceNewConfig; // force re-create with new config next time
     boolean supportsEnterPipOnTaskSwitch;  // This flag is set by the system to indicate that the
         // activity can enter picture in picture while pausing (only when switching to another task)
     // The PiP params used when deferring the entering of picture-in-picture.
@@ -9599,7 +9599,7 @@
         // configurations because there are cases (like moving a task to the root pinned task) where
         // the combine configurations are equal, but would otherwise differ in the override config
         mTmpConfig.setTo(mLastReportedConfiguration.getMergedConfiguration());
-        if (getConfiguration().equals(mTmpConfig) && !forceNewConfig && !displayChanged) {
+        if (getConfiguration().equals(mTmpConfig) && !displayChanged) {
             ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration & display "
                     + "unchanged in %s", this);
             return true;
@@ -9626,7 +9626,7 @@
             return true;
         }
 
-        if (changes == 0 && !forceNewConfig) {
+        if (changes == 0) {
             ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration no differences in %s",
                     this);
             // There are no significant differences, so we won't relaunch but should still deliver
@@ -9648,7 +9648,6 @@
         // pick that up next time it starts.
         if (!attachedToProcess()) {
             ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration doesn't matter not running %s", this);
-            forceNewConfig = false;
             return true;
         }
 
@@ -9658,11 +9657,10 @@
                 Integer.toHexString(changes), Integer.toHexString(info.getRealConfigChanged()),
                 mLastReportedConfiguration);
 
-        if (shouldRelaunchLocked(changes, mTmpConfig) || forceNewConfig) {
+        if (shouldRelaunchLocked(changes, mTmpConfig)) {
             // Aha, the activity isn't handling the change, so DIE DIE DIE.
             configChangeFlags |= changes;
             startFreezingScreenLocked(globalChanges);
-            forceNewConfig = false;
             // Do not preserve window if it is freezing screen because the original window won't be
             // able to update drawn state that causes freeze timeout.
             preserveWindow &= isResizeOnlyChange(changes) && !mFreezingScreen;
@@ -9882,7 +9880,6 @@
         try {
             ProtoLog.i(WM_DEBUG_STATES, "Moving to %s Relaunching %s callers=%s" ,
                     (andResume ? "RESUMED" : "PAUSED"), this, Debug.getCallers(6));
-            forceNewConfig = false;
             final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(token,
                     pendingResults, pendingNewIntents, configChangeFlags,
                     new MergedConfiguration(getProcessGlobalConfiguration(),
@@ -10357,6 +10354,8 @@
                 mLetterboxUiController.shouldRefreshActivityForCameraCompat());
         proto.write(SHOULD_REFRESH_ACTIVITY_VIA_PAUSE_FOR_CAMERA_COMPAT,
                 mLetterboxUiController.shouldRefreshActivityViaPauseForCameraCompat());
+        proto.write(SHOULD_OVERRIDE_MIN_ASPECT_RATIO,
+                mLetterboxUiController.shouldOverrideMinAspectRatio());
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
index 86be6ba..7af494c 100644
--- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java
+++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
@@ -33,6 +33,7 @@
 import com.android.server.LocalServices;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider;
+import com.android.window.flags.Flags;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -121,7 +122,8 @@
 
     // TODO remove when enabled
     static boolean isSnapshotEnabled() {
-        return SystemProperties.getInt("persist.wm.debug.activity_screenshot", 0) != 0;
+        return SystemProperties.getInt("persist.wm.debug.activity_screenshot", 0) != 0
+                || Flags.activitySnapshotByDefault();
     }
 
     static PersistInfoProvider createPersistInfoProvider(
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index f9d344b..e7621ff 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -48,6 +48,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.SuspendDialogInfo;
 import android.content.pm.UserInfo;
+import android.content.pm.UserPackage;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -223,7 +224,7 @@
             // before issuing the work challenge.
             return true;
         }
-        if (interceptLockedManagedProfileIfNeeded()) {
+        if (interceptLockedProfileIfNeeded()) {
             return true;
         }
         if (interceptHomeIfNeeded()) {
@@ -335,19 +336,19 @@
             return false;
         }
         final String suspendedPackage = mAInfo.applicationInfo.packageName;
-        final String suspendingPackage = pmi.getSuspendingPackage(suspendedPackage, mUserId);
-        if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) {
+        final UserPackage suspender = pmi.getSuspendingPackage(suspendedPackage, mUserId);
+        if (suspender != null && PLATFORM_PACKAGE_NAME.equals(suspender.packageName)) {
             return interceptSuspendedByAdminPackage();
         }
         final SuspendDialogInfo dialogInfo = pmi.getSuspendedDialogInfo(suspendedPackage,
-                suspendingPackage, mUserId);
+                suspender, mUserId);
         final Bundle crossProfileOptions = hasCrossProfileAnimation()
                 ? ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle()
                 : null;
         final IntentSender target = createIntentSenderForOriginalIntent(mCallingUid,
                 FLAG_IMMUTABLE);
         mIntent = SuspendedAppActivity.createSuspendedAppInterceptIntent(suspendedPackage,
-                suspendingPackage, dialogInfo, crossProfileOptions, target, mUserId);
+                suspender, dialogInfo, crossProfileOptions, target, mUserId);
         mCallingPid = mRealCallingPid;
         mCallingUid = mRealCallingUid;
         mResolvedType = null;
@@ -377,7 +378,7 @@
         return true;
     }
 
-    private boolean interceptLockedManagedProfileIfNeeded() {
+    private boolean interceptLockedProfileIfNeeded() {
         final Intent interceptingIntent = interceptWithConfirmCredentialsIfNeeded(mAInfo, mUserId);
         if (interceptingIntent == null) {
             return false;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 630b9e1..cb2adbc 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -579,30 +579,11 @@
                     computeResolveFilterUid(callingUid, realCallingUid, filterCallingUid),
                     realCallingPid);
             if (resolveInfo == null) {
-                final UserInfo userInfo = supervisor.getUserInfo(userId);
-                if (userInfo != null && userInfo.isManagedProfile()) {
-                    // Special case for managed profiles, if attempting to launch non-cryto aware
-                    // app in a locked managed profile from an unlocked parent allow it to resolve
-                    // as user will be sent via confirm credentials to unlock the profile.
-                    final UserManager userManager = UserManager.get(supervisor.mService.mContext);
-                    boolean profileLockedAndParentUnlockingOrUnlocked = false;
-                    final long token = Binder.clearCallingIdentity();
-                    try {
-                        final UserInfo parent = userManager.getProfileParent(userId);
-                        profileLockedAndParentUnlockingOrUnlocked = (parent != null)
-                                && userManager.isUserUnlockingOrUnlocked(parent.id)
-                                && !userManager.isUserUnlockingOrUnlocked(userId);
-                    } finally {
-                        Binder.restoreCallingIdentity(token);
-                    }
-                    if (profileLockedAndParentUnlockingOrUnlocked) {
-                        resolveInfo = supervisor.resolveIntent(intent, resolvedType, userId,
-                                PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
-                                computeResolveFilterUid(callingUid, realCallingUid,
-                                        filterCallingUid), realCallingPid);
-                    }
-                }
+                // Special case for profiles: If attempting to launch non-crypto aware app in a
+                // locked profile or launch an app in a profile that is stopped by quiet mode from
+                // an unlocked parent, allow it to resolve as user will be sent via confirm
+                // credentials to unlock the profile.
+                resolveInfo = resolveIntentForLockedOrStoppedProfiles(supervisor);
             }
 
             // Collect information about the target of the Intent.
@@ -616,6 +597,36 @@
                         UserHandle.getUserId(activityInfo.applicationInfo.uid));
             }
         }
+
+        /**
+         * Resolve intent for locked or stopped profiles if the parent profile is unlocking or
+         * unlocked.
+         */
+        ResolveInfo resolveIntentForLockedOrStoppedProfiles(
+                ActivityTaskSupervisor supervisor) {
+            final UserInfo userInfo = supervisor.getUserInfo(userId);
+            if (userInfo != null && userInfo.isProfile()) {
+                final UserManager userManager = UserManager.get(supervisor.mService.mContext);
+                boolean profileLockedAndParentUnlockingOrUnlocked = false;
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    final UserInfo parent = userManager.getProfileParent(userId);
+                    profileLockedAndParentUnlockingOrUnlocked = (parent != null)
+                            && userManager.isUserUnlockingOrUnlocked(parent.id)
+                            && !userManager.isUserUnlockingOrUnlocked(userId);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+                if (profileLockedAndParentUnlockingOrUnlocked) {
+                    return supervisor.resolveIntent(intent, resolvedType, userId,
+                            PackageManager.MATCH_DIRECT_BOOT_AWARE
+                                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+                            computeResolveFilterUid(callingUid, realCallingUid,
+                                    filterCallingUid), realCallingPid);
+                }
+            }
+            return null;
+        }
     }
 
     ActivityStarter(ActivityStartController controller, ActivityTaskManagerService service,
@@ -2767,10 +2778,7 @@
             }
         }
 
-        // If the target task is not in the front, then we need to bring it to the front...
-        // except...  well, with SINGLE_TASK_LAUNCH it's not entirely clear. We'd like to have
-        // the same behavior as if a new instance was being started, which means not bringing it
-        // to the front if the caller is not itself in the front.
+        // If the target task is not in the front, then we need to bring it to the front.
         final boolean differentTopTask;
         if (mTargetRootTask.getDisplayArea() == mPreferredTaskDisplayArea) {
             final Task focusRootTask = mTargetRootTask.mDisplayContent.getFocusedRootTask();
@@ -2787,49 +2795,47 @@
 
         if (differentTopTask && !avoidMoveToFront()) {
             mStartActivity.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
-            if (mSourceRecord == null || inTopNonFinishingTask(mSourceRecord)) {
-                // We really do want to push this one into the user's face, right now.
-                if (mLaunchTaskBehind && mSourceRecord != null) {
-                    intentActivity.setTaskToAffiliateWith(mSourceRecord.getTask());
-                }
-
-                if (intentActivity.isDescendantOf(mTargetRootTask)) {
-                    // TODO(b/151572268): Figure out a better way to move tasks in above 2-levels
-                    //  tasks hierarchies.
-                    if (mTargetRootTask != intentTask
-                            && mTargetRootTask != intentTask.getParent().asTask()) {
-                        intentTask.getParent().positionChildAt(POSITION_TOP, intentTask,
-                                false /* includingParents */);
-                        intentTask = intentTask.getParent().asTaskFragment().getTask();
-                    }
-                    // If the activity is visible in multi-windowing mode, it may already be on
-                    // the top (visible to user but not the global top), then the result code
-                    // should be START_DELIVERED_TO_TOP instead of START_TASK_TO_FRONT.
-                    final boolean wasTopOfVisibleRootTask = intentActivity.isVisibleRequested()
-                            && intentActivity.inMultiWindowMode()
-                            && intentActivity == mTargetRootTask.topRunningActivity()
-                            && !intentActivity.mTransitionController.isTransientHide(
-                                    mTargetRootTask);
-                    // We only want to move to the front, if we aren't going to launch on a
-                    // different root task. If we launch on a different root task, we will put the
-                    // task on top there.
-                    // Defer resuming the top activity while moving task to top, since the
-                    // current task-top activity may not be the activity that should be resumed.
-                    mTargetRootTask.moveTaskToFront(intentTask, mNoAnimation, mOptions,
-                            mStartActivity.appTimeTracker, DEFER_RESUME,
-                            "bringingFoundTaskToFront");
-                    mMovedToFront = !wasTopOfVisibleRootTask;
-                } else if (intentActivity.getWindowingMode() != WINDOWING_MODE_PINNED) {
-                    // Leaves reparenting pinned task operations to task organizer to make sure it
-                    // dismisses pinned task properly.
-                    // TODO(b/199997762): Consider leaving all reparent operation of organized tasks
-                    //  to task organizer.
-                    intentTask.reparent(mTargetRootTask, ON_TOP, REPARENT_MOVE_ROOT_TASK_TO_FRONT,
-                            ANIMATE, DEFER_RESUME, "reparentToTargetRootTask");
-                    mMovedToFront = true;
-                }
-                mOptions = null;
+            // We really do want to push this one into the user's face, right now.
+            if (mLaunchTaskBehind && mSourceRecord != null) {
+                intentActivity.setTaskToAffiliateWith(mSourceRecord.getTask());
             }
+
+            if (intentActivity.isDescendantOf(mTargetRootTask)) {
+                // TODO(b/151572268): Figure out a better way to move tasks in above 2-levels
+                //  tasks hierarchies.
+                if (mTargetRootTask != intentTask
+                        && mTargetRootTask != intentTask.getParent().asTask()) {
+                    intentTask.getParent().positionChildAt(POSITION_TOP, intentTask,
+                            false /* includingParents */);
+                    intentTask = intentTask.getParent().asTaskFragment().getTask();
+                }
+                // If the activity is visible in multi-windowing mode, it may already be on
+                // the top (visible to user but not the global top), then the result code
+                // should be START_DELIVERED_TO_TOP instead of START_TASK_TO_FRONT.
+                final boolean wasTopOfVisibleRootTask = intentActivity.isVisibleRequested()
+                        && intentActivity.inMultiWindowMode()
+                        && intentActivity == mTargetRootTask.topRunningActivity()
+                        && !intentActivity.mTransitionController.isTransientHide(
+                                mTargetRootTask);
+                // We only want to move to the front, if we aren't going to launch on a
+                // different root task. If we launch on a different root task, we will put the
+                // task on top there.
+                // Defer resuming the top activity while moving task to top, since the
+                // current task-top activity may not be the activity that should be resumed.
+                mTargetRootTask.moveTaskToFront(intentTask, mNoAnimation, mOptions,
+                        mStartActivity.appTimeTracker, DEFER_RESUME,
+                        "bringingFoundTaskToFront");
+                mMovedToFront = !wasTopOfVisibleRootTask;
+            } else if (intentActivity.getWindowingMode() != WINDOWING_MODE_PINNED) {
+                // Leaves reparenting pinned task operations to task organizer to make sure it
+                // dismisses pinned task properly.
+                // TODO(b/199997762): Consider leaving all reparent operation of organized tasks
+                //  to task organizer.
+                intentTask.reparent(mTargetRootTask, ON_TOP, REPARENT_MOVE_ROOT_TASK_TO_FRONT,
+                        ANIMATE, DEFER_RESUME, "reparentToTargetRootTask");
+                mMovedToFront = true;
+            }
+            mOptions = null;
         }
         if (differentTopTask) {
             logPIOnlyCreatorAllowsBAL();
@@ -2850,20 +2856,6 @@
                 mRootWindowContainer.getDefaultTaskDisplayArea(), mTargetRootTask);
     }
 
-    private boolean inTopNonFinishingTask(ActivityRecord r) {
-        if (r == null || r.getTask() == null) {
-            return false;
-        }
-
-        final Task rTask = r.getTask();
-        final Task parent = rTask.getCreatedByOrganizerTask() != null
-                ? rTask.getCreatedByOrganizerTask() : r.getRootTask();
-        final ActivityRecord topNonFinishingActivity = parent != null
-                ? parent.getTopNonFinishingActivity() : null;
-
-        return topNonFinishingActivity != null && topNonFinishingActivity.getTask() == rTask;
-    }
-
     private void resumeTargetRootTaskIfNeeded() {
         if (mDoResume) {
             final ActivityRecord next = mTargetRootTask.topRunningActivity(
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 869bcc0..908c49e 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3695,6 +3695,21 @@
         boolean originallyFromClient = fromClient
                 && (!r.isState(PAUSING) || params.isAutoEnterEnabled());
 
+        // If PiP2 flag is on and client-request to enter PiP came via onUserLeaveHint(),
+        // we request a direct transition from Shell to TRANSIT_PIP_LEGACY to get the startWct
+        // with the right entry bounds.
+        if (isPip2ExperimentEnabled() && !originallyFromClient && !params.isAutoEnterEnabled()) {
+            final Transition legacyEnterPipTransition = new Transition(TRANSIT_PIP,
+                    0 /* flags */, getTransitionController(),
+                    mWindowManager.mSyncEngine);
+            legacyEnterPipTransition.setPipActivity(r);
+            getTransitionController().startCollectOrQueue(legacyEnterPipTransition, (deferred) -> {
+                getTransitionController().requestStartTransition(legacyEnterPipTransition,
+                        r.getTask(), null /* remoteTransition */, null /* displayChange */);
+            });
+            return true;
+        }
+
         // Create a transition only for this pip entry if it is coming from the app without the
         // system requesting that the app enter-pip. If the system requested it, that means it
         // should be part of that transition if possible.
@@ -4762,6 +4777,10 @@
         if (DEBUG_ALL && !mWindowManager.mWindowPlacerLocked.isLayoutDeferred()) {
             Slog.i(TAG, "continueWindowLayout reason=" + mLayoutReasons);
         }
+
+        // ClientTransactions is queued during #deferWindowLayout() for performance.
+        // Notify to continue.
+        mLifecycleManager.onLayoutContinued();
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index e59601c..10efb94 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -906,7 +906,6 @@
                 }
                 mService.getPackageManagerInternalLocked().notifyPackageUse(
                         r.intent.getComponent().getPackageName(), NOTIFY_PACKAGE_USE_ACTIVITY);
-                r.forceNewConfig = false;
                 mService.getAppWarningsLocked().onStartActivity(r);
 
                 // Because we could be starting an Activity in the system process this may not go
diff --git a/services/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
index 6b6388b..2e47677 100644
--- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java
+++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
@@ -105,7 +105,7 @@
             final ClientTransaction clientTransaction = getOrCreatePendingTransaction(client);
             clientTransaction.addTransactionItem(transactionItem);
 
-            onClientTransactionItemScheduledLocked(clientTransaction);
+            onClientTransactionItemScheduled(clientTransaction);
         } else {
             // TODO(b/260873529): cleanup after launch.
             final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
@@ -134,7 +134,7 @@
             clientTransaction.addTransactionItem(transactionItem);
             clientTransaction.addTransactionItem(lifecycleItem);
 
-            onClientTransactionItemScheduledLocked(clientTransaction);
+            onClientTransactionItemScheduled(clientTransaction);
         } else {
             // TODO(b/260873529): cleanup after launch.
             final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
@@ -146,6 +146,9 @@
 
     /** Executes all the pending transactions. */
     void dispatchPendingTransactions() {
+        if (!Flags.bundleClientTransactionFlag()) {
+            return;
+        }
         final int size = mPendingTransactions.size();
         for (int i = 0; i < size; i++) {
             final ClientTransaction transaction = mPendingTransactions.valueAt(i);
@@ -158,6 +161,20 @@
         mPendingTransactions.clear();
     }
 
+    /**
+     * Called to when {@link WindowSurfacePlacer#continueLayout}.
+     * Dispatches all pending transactions unless there is an ongoing/scheduled layout, in which
+     * case the pending transactions will be dispatched in
+     * {@link RootWindowContainer#performSurfacePlacementNoTrace}.
+     */
+    void onLayoutContinued() {
+        if (shouldDispatchPendingTransactionsImmediately()) {
+            // Dispatch the pending transactions immediately if there is no ongoing/scheduled layout
+            dispatchPendingTransactions();
+        }
+    }
+
+    /** Must only be called with WM lock. */
     @NonNull
     private ClientTransaction getOrCreatePendingTransaction(@NonNull IApplicationThread client) {
         final IBinder clientBinder = client.asBinder();
@@ -173,20 +190,28 @@
     }
 
     /** Must only be called with WM lock. */
-    private void onClientTransactionItemScheduledLocked(
+    private void onClientTransactionItemScheduled(
             @NonNull ClientTransaction clientTransaction) throws RemoteException {
-        // TODO(b/260873529): make sure WindowSurfacePlacer#requestTraversal is called before
-        // ClientTransaction scheduled when needed.
-
-        if (mWms != null && (mWms.mWindowPlacerLocked.isInLayout()
-                || mWms.mWindowPlacerLocked.isTraversalScheduled())) {
-            // The pending transactions will be dispatched when
-            // RootWindowContainer#performSurfacePlacementNoTrace.
-            return;
+        if (shouldDispatchPendingTransactionsImmediately()) {
+            // Dispatch the pending transaction immediately.
+            mPendingTransactions.remove(clientTransaction.getClient().asBinder());
+            scheduleTransaction(clientTransaction);
         }
+    }
 
-        // Dispatch the pending transaction immediately.
-        mPendingTransactions.remove(clientTransaction.getClient().asBinder());
-        scheduleTransaction(clientTransaction);
+    /** Must only be called with WM lock. */
+    private boolean shouldDispatchPendingTransactionsImmediately() {
+        if (mWms == null) {
+            return true;
+        }
+        // Do not dispatch when
+        // 1. Layout deferred.
+        // 2. Layout requested.
+        // 3. Layout in process.
+        // The pending transactions will be dispatched during layout in
+        // RootWindowContainer#performSurfacePlacementNoTrace.
+        return !mWms.mWindowPlacerLocked.isLayoutDeferred()
+                && !mWms.mWindowPlacerLocked.isTraversalScheduled()
+                && !mWms.mWindowPlacerLocked.isInLayout();
     }
 }
diff --git a/services/core/java/com/android/server/wm/CompatModePackages.java b/services/core/java/com/android/server/wm/CompatModePackages.java
index 73bcc8d..1a8927e 100644
--- a/services/core/java/com/android/server/wm/CompatModePackages.java
+++ b/services/core/java/com/android/server/wm/CompatModePackages.java
@@ -19,7 +19,6 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.CompatScaleProvider.COMPAT_SCALE_MODE_SYSTEM_FIRST;
 import static com.android.server.wm.CompatScaleProvider.COMPAT_SCALE_MODE_SYSTEM_LAST;
 
@@ -47,6 +46,7 @@
 import android.util.DisplayMetrics;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.util.Xml;
 
 import com.android.internal.protolog.common.ProtoLog;
@@ -60,6 +60,7 @@
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
@@ -347,6 +348,7 @@
     private GameManagerInternal mGameManager;
     private final AtomicFile mFile;
     private final HashMap<String, Integer> mPackages = new HashMap<>();
+    private final SparseBooleanArray mLegacyScreenCompatPackages = new SparseBooleanArray();
     private final CompatHandler mHandler;
 
     private final SparseArray<CompatScaleProvider> mProviders = new SparseArray<>();
@@ -427,6 +429,7 @@
             mPackages.remove(packageName);
             scheduleWrite();
         }
+        mLegacyScreenCompatPackages.delete(packageName.hashCode());
     }
 
     public void handlePackageAddedLocked(String packageName, boolean updated) {
@@ -458,6 +461,17 @@
         mHandler.sendMessageDelayed(msg, 10000);
     }
 
+    /**
+     * Returns {@code true} if the windows belonging to the package should be scaled with
+     * {@link DisplayContent#mCompatibleScreenScale}.
+     */
+    boolean useLegacyScreenCompatMode(String packageName) {
+        if (mLegacyScreenCompatPackages.size() == 0) {
+            return false;
+        }
+        return mLegacyScreenCompatPackages.get(packageName.hashCode());
+    }
+
     public CompatibilityInfo compatibilityInfoForPackageLocked(ApplicationInfo ai) {
         final boolean forceCompat = getPackageCompatModeEnabledLocked(ai);
         final CompatScale compatScale = getCompatScaleFromProvider(ai.packageName, ai.uid);
@@ -466,8 +480,18 @@
                 : getCompatScale(ai.packageName, ai.uid, /* checkProvider= */ false);
         final float densityScale = compatScale != null ? compatScale.mDensityScaleFactor : appScale;
         final Configuration config = mService.getGlobalConfiguration();
-        return new CompatibilityInfo(ai, config.screenLayout, config.smallestScreenWidthDp,
-                forceCompat, appScale, densityScale);
+        final CompatibilityInfo info = new CompatibilityInfo(ai, config.screenLayout,
+                config.smallestScreenWidthDp, forceCompat, appScale, densityScale);
+        // Ignore invalid info which may be a placeholder of isolated process.
+        if (ai.flags != 0 && ai.sourceDir != null) {
+            if (!info.supportsScreen() && !"android".equals(ai.packageName)) {
+                Slog.i(TAG, "Use legacy screen compat mode: " + ai.packageName);
+                mLegacyScreenCompatPackages.put(ai.packageName.hashCode(), true);
+            } else if (mLegacyScreenCompatPackages.size() > 0) {
+                mLegacyScreenCompatPackages.delete(ai.packageName.hashCode());
+            }
+        }
+        return info;
     }
 
     float getCompatScale(String packageName, int uid) {
@@ -718,14 +742,23 @@
 
             scheduleWrite();
 
-            final Task rootTask = mService.getTopDisplayFocusedRootTask();
-            ActivityRecord starting = rootTask.restartPackage(packageName);
-
+            final ArrayList<WindowProcessController> restartedApps = new ArrayList<>();
+            mService.mRootWindowContainer.forAllWindows(w -> {
+                final ActivityRecord ar = w.mActivityRecord;
+                if (ar != null) {
+                    if (ar.packageName.equals(packageName) && !restartedApps.contains(ar.app)) {
+                        ar.restartProcessIfVisible();
+                        restartedApps.add(ar.app);
+                    }
+                } else if (w.getProcess().mInfo.packageName.equals(packageName)) {
+                    w.updateGlobalScale();
+                }
+            }, true /* traverseTopToBottom */);
             // Tell all processes that loaded this package about the change.
             SparseArray<WindowProcessController> pidMap = mService.mProcessMap.getPidMap();
             for (int i = pidMap.size() - 1; i >= 0; i--) {
                 final WindowProcessController app = pidMap.valueAt(i);
-                if (!app.containsPackage(packageName)) {
+                if (!app.containsPackage(packageName) || restartedApps.contains(app)) {
                     continue;
                 }
                 try {
@@ -737,14 +770,6 @@
                 } catch (Exception e) {
                 }
             }
-
-            if (starting != null) {
-                starting.ensureActivityConfiguration(0 /* globalChanges */,
-                        false /* preserveWindow */);
-                // And we need to make sure at this point that all other activities
-                // are made visible with the correct configuration.
-                rootTask.ensureActivitiesVisible(starting, 0, !PRESERVE_WINDOWS);
-            }
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ae10ce3..f8dc9c7 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -512,7 +512,11 @@
      */
     private final DisplayMetrics mCompatDisplayMetrics = new DisplayMetrics();
 
-    /** The desired scaling factor for compatible apps. */
+    /**
+     * The desired scaling factor for compatible apps. It limits the size of the window to be
+     * original size ([320x480] x density). Used to scale window for applications running under
+     * legacy compatibility mode.
+     */
     float mCompatibleScreenScale;
 
     /** @see #getCurrentOverrideConfigurationChanges */
@@ -4794,7 +4798,8 @@
             assignRelativeLayerForIme(getSyncTransaction(), true /* forceUpdate */);
             scheduleAnimation();
 
-            mWmService.mH.post(() -> InputMethodManagerInternal.get().onImeParentChanged());
+            mWmService.mH.post(
+                    () -> InputMethodManagerInternal.get().onImeParentChanged(getDisplayId()));
         } else if (mImeControlTarget != null && mImeControlTarget == mImeLayeringTarget) {
             // Even if the IME surface parent is not changed, the layer target belonging to the
             // parent may have changes. Then attempt to reassign if the IME control target is
@@ -7090,7 +7095,7 @@
         }
 
         @Override
-        public void notifyInsetsControlChanged() {
+        public void notifyInsetsControlChanged(int displayId) {
             final InsetsStateController stateController = getInsetsStateController();
             try {
                 mRemoteInsetsController.insetsControlChanged(stateController.getRawInsetsState(),
diff --git a/services/core/java/com/android/server/wm/InsetsControlTarget.java b/services/core/java/com/android/server/wm/InsetsControlTarget.java
index 8ecbc17..b74eb56 100644
--- a/services/core/java/com/android/server/wm/InsetsControlTarget.java
+++ b/services/core/java/com/android/server/wm/InsetsControlTarget.java
@@ -29,8 +29,10 @@
 
     /**
      * Notifies the control target that the insets control has changed.
+     *
+     * @param displayId the display hosting the window of this target
      */
-    default void notifyInsetsControlChanged() {
+    default void notifyInsetsControlChanged(int displayId) {
     };
 
     /**
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 7815679..3c556bf 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -728,7 +728,7 @@
         }
 
         @Override
-        public void notifyInsetsControlChanged() {
+        public void notifyInsetsControlChanged(int displayId) {
             mHandler.post(this);
         }
 
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index c4d0129..6b9fcf4 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -72,7 +72,7 @@
     };
     private final InsetsControlTarget mEmptyImeControlTarget = new InsetsControlTarget() {
         @Override
-        public void notifyInsetsControlChanged() {
+        public void notifyInsetsControlChanged(int displayId) {
             InsetsSourceControl[] controls = getControlsForDispatch(this);
             if (controls == null) {
                 return;
@@ -80,7 +80,7 @@
             for (InsetsSourceControl control : controls) {
                 if (control.getType() == WindowInsets.Type.ime()) {
                     mDisplayContent.mWmService.mH.post(() ->
-                            InputMethodManagerInternal.get().removeImeSurface());
+                            InputMethodManagerInternal.get().removeImeSurface(displayId));
                 }
             }
         }
@@ -370,9 +370,10 @@
                 provider.onSurfaceTransactionApplied();
             }
             final ArraySet<InsetsControlTarget> newControlTargets = new ArraySet<>();
+            int displayId = mDisplayContent.getDisplayId();
             for (int i = mPendingControlChanged.size() - 1; i >= 0; i--) {
                 final InsetsControlTarget controlTarget = mPendingControlChanged.valueAt(i);
-                controlTarget.notifyInsetsControlChanged();
+                controlTarget.notifyInsetsControlChanged(displayId);
                 if (mControlTargetProvidersMap.containsKey(controlTarget)) {
                     // We only collect targets who get controls, not lose controls.
                     newControlTargets.add(controlTarget);
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 5518de7..9305396 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -20,6 +20,7 @@
 import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP;
 import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
 import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION;
+import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION_TO_USER;
 import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
 import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
 import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
@@ -170,6 +171,8 @@
 
     // Corresponds to OVERRIDE_ANY_ORIENTATION
     private final boolean mIsOverrideAnyOrientationEnabled;
+    // Corresponds to OVERRIDE_ANY_ORIENTATION_TO_USER
+    private final boolean mIsOverrideToUserOrientationEnabled;
     // Corresponds to OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT
     private final boolean mIsOverrideToPortraitOrientationEnabled;
     // Corresponds to OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR
@@ -254,9 +257,8 @@
     // Counter for ActivityRecord#setRequestedOrientation
     private int mSetOrientationRequestCounter = 0;
 
-    // The min aspect ratio override set by user. Stores the last selected aspect ratio after
-    // {@link #shouldApplyUserFullscreenOverride} or {@link #shouldApplyUserMinAspectRatioOverride}
-    // have been invoked.
+    // TODO(b/315140179): Make mUserAspectRatio final
+    // The min aspect ratio override set by user
     @PackageManager.UserMinAspectRatio
     private int mUserAspectRatio = USER_MIN_ASPECT_RATIO_UNSET;
 
@@ -355,6 +357,8 @@
                         PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE);
 
         mIsOverrideAnyOrientationEnabled = isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION);
+        mIsOverrideToUserOrientationEnabled =
+                isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION_TO_USER);
         mIsOverrideToPortraitOrientationEnabled =
                 isCompatChangeEnabled(OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT);
         mIsOverrideToReverseLandscapeOrientationEnabled =
@@ -663,9 +667,11 @@
 
     @ScreenOrientation
     int overrideOrientationIfNeeded(@ScreenOrientation int candidate) {
+        final DisplayContent displayContent = mActivityRecord.mDisplayContent;
+        final boolean isIgnoreOrientationRequestEnabled = displayContent != null
+                && displayContent.getIgnoreOrientationRequest();
         if (shouldApplyUserFullscreenOverride()
-                && mActivityRecord.mDisplayContent != null
-                && mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()) {
+                && isIgnoreOrientationRequestEnabled) {
             Slog.v(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for "
                     + mActivityRecord + " is overridden to "
                     + screenOrientationToString(SCREEN_ORIENTATION_USER)
@@ -690,7 +696,6 @@
             return candidate;
         }
 
-        DisplayContent displayContent = mActivityRecord.mDisplayContent;
         if (mIsOverrideOrientationOnlyForCameraEnabled && displayContent != null
                 && (displayContent.mDisplayRotationCompatPolicy == null
                         || !displayContent.mDisplayRotationCompatPolicy
@@ -698,6 +703,17 @@
             return candidate;
         }
 
+        // mUserAspectRatio is always initialized first in shouldApplyUserFullscreenOverride(),
+        // which will always come first before this check as user override > device
+        // manufacturer override.
+        if (mUserAspectRatio == PackageManager.USER_MIN_ASPECT_RATIO_UNSET
+                && mIsOverrideToUserOrientationEnabled && isIgnoreOrientationRequestEnabled) {
+            Slog.v(TAG, "Requested orientation  " + screenOrientationToString(candidate) + " for "
+                    + mActivityRecord + " is overridden to "
+                    + screenOrientationToString(SCREEN_ORIENTATION_USER));
+            return SCREEN_ORIENTATION_USER;
+        }
+
         if (mIsOverrideToReverseLandscapeOrientationEnabled
                 && (isFixedOrientationLandscape(candidate) || mIsOverrideAnyOrientationEnabled)) {
             Slog.w(TAG, "Requested orientation  " + screenOrientationToString(candidate) + " for "
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 57939bc..7995028 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -89,6 +89,7 @@
 import com.android.window.flags.Flags;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.function.BiConsumer;
@@ -106,7 +107,7 @@
     final WindowProcessController mProcess;
     private final String mStringName;
     SurfaceSession mSurfaceSession;
-    private int mNumWindow = 0;
+    private final ArrayList<WindowState> mAddedWindows = new ArrayList<>();
     // Set of visible application overlay window surfaces connected to this session.
     private final ArraySet<WindowSurfaceController> mAppOverlaySurfaces = new ArraySet<>();
     // Set of visible alert window surfaces connected to this session.
@@ -192,8 +193,7 @@
         try {
             mCallback.asBinder().linkToDeath(this, 0);
         } catch (RemoteException e) {
-            // The caller has died, so we can just forget about this.
-            // Hmmm, should we call killSessionLocked()??
+            mClientDead = true;
         }
     }
 
@@ -211,12 +211,27 @@
         }
     }
 
+    boolean isClientDead() {
+        return mClientDead;
+    }
+
     @Override
     public void binderDied() {
         synchronized (mService.mGlobalLock) {
             mCallback.asBinder().unlinkToDeath(this, 0);
             mClientDead = true;
-            killSessionLocked();
+            try {
+                for (int i = mAddedWindows.size() - 1; i >= 0; i--) {
+                    final WindowState w = mAddedWindows.get(i);
+                    Slog.i(TAG_WM, "WIN DEATH: " + w);
+                    if (w.mActivityRecord != null && w.mActivityRecord.findMainWindow() == w) {
+                        mService.mSnapshotController.onAppDied(w.mActivityRecord);
+                    }
+                    w.removeIfPossible();
+                }
+            } finally {
+                killSessionLocked();
+            }
         }
     }
 
@@ -741,7 +756,7 @@
         }
     }
 
-    void windowAddedLocked() {
+    void onWindowAdded(WindowState w) {
         if (mPackageName == null) {
             mPackageName = mProcess.mInfo.packageName;
             mRelayoutTag = "relayoutWindow: " + mPackageName;
@@ -757,12 +772,14 @@
                 mService.dispatchNewAnimatorScaleLocked(this);
             }
         }
-        mNumWindow++;
+        mAddedWindows.add(w);
     }
 
-    void windowRemovedLocked() {
-        mNumWindow--;
-        killSessionLocked();
+    void onWindowRemoved(WindowState w) {
+        mAddedWindows.remove(w);
+        if (mAddedWindows.isEmpty()) {
+            killSessionLocked();
+        }
     }
 
 
@@ -829,7 +846,7 @@
     }
 
     private void killSessionLocked() {
-        if (mNumWindow > 0 || !mClientDead) {
+        if (!mClientDead) {
             return;
         }
 
@@ -838,10 +855,6 @@
             return;
         }
 
-        if (DEBUG) {
-            Slog.v(TAG_WM, "Last window removed from " + this
-                    + ", destroying " + mSurfaceSession);
-        }
         ProtoLog.i(WM_SHOW_TRANSACTIONS, "  KILL SURFACE SESSION %s", mSurfaceSession);
         try {
             mSurfaceSession.kill();
@@ -850,6 +863,7 @@
                     + " in session " + this + ": " + e.toString());
         }
         mSurfaceSession = null;
+        mAddedWindows.clear();
         mAlertWindowSurfaces.clear();
         mAppOverlaySurfaces.clear();
         setHasOverlayUi(false);
@@ -869,7 +883,7 @@
     }
 
     void dump(PrintWriter pw, String prefix) {
-        pw.print(prefix); pw.print("mNumWindow="); pw.print(mNumWindow);
+        pw.print(prefix); pw.print("numWindow="); pw.print(mAddedWindows.size());
                 pw.print(" mCanAddInternalSystemWindow="); pw.print(mCanAddInternalSystemWindow);
                 pw.print(" mAppOverlaySurfaces="); pw.print(mAppOverlaySurfaces);
                 pw.print(" mAlertWindowSurfaces="); pw.print(mAlertWindowSurfaces);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 671acfc..dbfcc22 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -34,7 +34,6 @@
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
 import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
-import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
 import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
 import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
@@ -5917,22 +5916,6 @@
         return activities;
     }
 
-    ActivityRecord restartPackage(String packageName) {
-        ActivityRecord starting = topRunningActivity();
-
-        // All activities that came from the package must be
-        // restarted as if there was a config change.
-        forAllActivities(r -> {
-            if (!r.info.packageName.equals(packageName)) return;
-            r.forceNewConfig = true;
-            if (starting != null && r == starting && r.isVisibleRequested()) {
-                r.startFreezingScreenLocked(CONFIG_SCREEN_LAYOUT);
-            }
-        });
-
-        return starting;
-    }
-
     Task reuseOrCreateTask(ActivityInfo info, Intent intent, boolean toTop) {
         return reuseOrCreateTask(info, intent, null /*voiceSession*/, null /*voiceInteractor*/,
                 toTop, null /*activity*/, null /*source*/, null /*options*/);
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index e648d64..c3aca6f 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -731,9 +731,9 @@
             }
 
             // set the pip task in the request if provided
-            if (mCollectingTransition.getPipActivity() != null) {
-                pipTaskInfo = mCollectingTransition.getPipActivity().getTask().getTaskInfo();
-                mCollectingTransition.setPipActivity(null);
+            if (transition.getPipActivity() != null) {
+                pipTaskInfo = transition.getPipActivity().getTask().getTaskInfo();
+                transition.setPipActivity(null);
             }
 
             final TransitionRequestInfo request = new TransitionRequestInfo(transition.mType,
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 2125c63..c1310a6 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1445,6 +1445,11 @@
             if (!mDisplayReady) {
                 throw new IllegalStateException("Display has not been initialialized");
             }
+            if (session.isClientDead()) {
+                ProtoLog.w(WM_ERROR, "Attempted to add window with a client %s "
+                        + "that is dead. Aborting.", session);
+                return WindowManagerGlobal.ADD_APP_EXITING;
+            }
 
             final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);
 
@@ -1629,19 +1634,6 @@
             final WindowState win = new WindowState(this, session, client, token, parentWindow,
                     appOp[0], attrs, viewVisibility, session.mUid, userId,
                     session.mCanAddInternalSystemWindow);
-            if (win.mDeathRecipient == null) {
-                // Client has apparently died, so there is no reason to
-                // continue.
-                ProtoLog.w(WM_ERROR, "Adding window client %s"
-                        + " that is dead, aborting.", client.asBinder());
-                return WindowManagerGlobal.ADD_APP_EXITING;
-            }
-
-            if (win.getDisplayContent() == null) {
-                ProtoLog.w(WM_ERROR, "Adding window to Display that has been removed.");
-                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
-            }
-
             final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
             displayPolicy.adjustWindowParamsLw(win, win.mAttrs);
             attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), callingUid, callingPid);
@@ -1725,7 +1717,7 @@
                 displayContent.mTapExcludedWindows.add(win);
             }
 
-            win.attach();
+            win.mSession.onWindowAdded(win);
             mWindowMap.put(client.asBinder(), win);
             win.initAppOpsState();
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 4e9d23c..9c21e4c 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -52,7 +52,6 @@
 import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NOT_MAGNIFIABLE;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY;
@@ -310,7 +309,6 @@
     // mAttrs.flags is tested in animation without being locked. If the bits tested are ever
     // modified they will need to be locked.
     final WindowManager.LayoutParams mAttrs = new WindowManager.LayoutParams();
-    final DeathRecipient mDeathRecipient;
     private boolean mIsChildWindow;
     final int mBaseLayer;
     final int mSubLayer;
@@ -1108,7 +1106,6 @@
         mViewVisibility = viewVisibility;
         mPolicy = mWmService.mPolicy;
         mContext = mWmService.mContext;
-        DeathRecipient deathRecipient = new DeathRecipient();
         mPowerManagerWrapper = powerManagerWrapper;
         mForceSeamlesslyRotate = token.mRoundedCornerOverlay;
         mInputWindowHandle = new InputWindowHandleWrapper(new InputWindowHandle(
@@ -1128,22 +1125,6 @@
             Slog.v(TAG, "Window " + this + " client=" + c.asBinder()
                             + " token=" + token + " (" + mAttrs.token + ")" + " params=" + a);
         }
-        try {
-            c.asBinder().linkToDeath(deathRecipient, 0);
-        } catch (RemoteException e) {
-            mDeathRecipient = null;
-            mIsChildWindow = false;
-            mLayoutAttached = false;
-            mIsImWindow = false;
-            mIsWallpaper = false;
-            mIsFloatingLayer = false;
-            mBaseLayer = 0;
-            mSubLayer = 0;
-            mWinAnimator = null;
-            mOverrideScale = 1f;
-            return;
-        }
-        mDeathRecipient = deathRecipient;
 
         if (mAttrs.type >= FIRST_SUB_WINDOW && mAttrs.type <= LAST_SUB_WINDOW) {
             // The multiplier here is to reserve space for multiple
@@ -1238,11 +1219,6 @@
         return TouchOcclusionMode.BLOCK_UNTRUSTED;
     }
 
-    void attach() {
-        if (DEBUG) Slog.v(TAG, "Attaching " + this + " token=" + mToken);
-        mSession.windowAddedLocked();
-    }
-
     void updateGlobalScale() {
         if (hasCompatScale()) {
             mCompatScale = (mOverrideScale == 1f || mToken.hasSizeCompatBounds())
@@ -1270,13 +1246,14 @@
      * @see ActivityRecord#hasSizeCompatBounds()
      */
     boolean hasCompatScale() {
-        if ((mAttrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0) {
-            return true;
-        }
         if (mAttrs.type == TYPE_APPLICATION_STARTING) {
             // Exclude starting window because it is not displayed by the application.
             return false;
         }
+        if (mWmService.mAtmService.mCompatModePackages.useLegacyScreenCompatMode(
+                mSession.mProcess.mInfo.packageName)) {
+            return true;
+        }
         return mActivityRecord != null && mActivityRecord.hasSizeCompatBounds()
                 || mOverrideScale != 1f;
     }
@@ -2398,14 +2375,7 @@
         disposeInputChannel();
         mOnBackInvokedCallbackInfo = null;
 
-        mSession.windowRemovedLocked();
-        try {
-            mClient.asBinder().unlinkToDeath(mDeathRecipient, 0);
-        } catch (RuntimeException e) {
-            // Ignore if it has already been removed (usually because
-            // we are doing this as part of processing a death note.)
-        }
-
+        mSession.onWindowRemoved(this);
         mWmService.postWindowRemoveCleanupLocked(this);
 
         mWmService.mTrustedPresentationListenerController.removeIgnoredWindowTokens(
@@ -2935,31 +2905,6 @@
         }
     }
 
-    private class DeathRecipient implements IBinder.DeathRecipient {
-        @Override
-        public void binderDied() {
-            try {
-                synchronized (mWmService.mGlobalLock) {
-                    final WindowState win = mWmService
-                            .windowForClientLocked(mSession, mClient, false);
-                    Slog.i(TAG, "WIN DEATH: " + win);
-                    if (win != null) {
-                        if (win.mActivityRecord != null
-                                && win.mActivityRecord.findMainWindow() == win) {
-                            mWmService.mSnapshotController.onAppDied(win.mActivityRecord);
-                        }
-                        win.removeIfPossible();
-                    } else if (mHasSurface) {
-                        Slog.e(TAG, "!!! LEAK !!! Window removed but surface still valid.");
-                        WindowState.this.removeIfPossible();
-                    }
-                }
-            } catch (IllegalArgumentException ex) {
-                // This will happen if the window has already been removed.
-            }
-        }
-    }
-
     /** Returns {@code true} if this window desires key events. */
     boolean canReceiveKeys() {
         return canReceiveKeys(false /* fromUserTouch */);
@@ -3830,7 +3775,7 @@
     }
 
     @Override
-    public void notifyInsetsControlChanged() {
+    public void notifyInsetsControlChanged(int displayId) {
         ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "notifyInsetsControlChanged for %s ", this);
         if (mRemoved) {
             return;
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index c34e701..b19f3d8 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -37,7 +37,6 @@
         "com_android_server_adb_AdbDebuggingManager.cpp",
         "com_android_server_am_BatteryStatsService.cpp",
         "com_android_server_biometrics_SurfaceToNativeHandleConverter.cpp",
-        "com_android_server_BootReceiver.cpp",
         "com_android_server_ConsumerIrService.cpp",
         "com_android_server_companion_virtual_InputController.cpp",
         "com_android_server_devicepolicy_CryptoTestHelper.cpp",
@@ -94,16 +93,6 @@
     header_libs: [
         "bionic_libc_platform_headers",
     ],
-
-    static_libs: [
-        "libunwindstack",
-    ],
-
-    whole_static_libs: [
-        "libdebuggerd_tombstone_proto_to_text",
-    ],
-
-    runtime_libs: ["libdexfile"],
 }
 
 cc_defaults {
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index 0d762dfb..061fe0f 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -33,4 +33,3 @@
 
 # Bug component : 158088 = per-file com_android_server_utils_AnrTimer*.java
 per-file com_android_server_utils_AnrTimer*.java = file:/PERFORMANCE_OWNERS
-per-file com_android_server_BootReceiver.cpp = file:/STABILITY_OWNERS
diff --git a/services/core/jni/com_android_server_BootReceiver.cpp b/services/core/jni/com_android_server_BootReceiver.cpp
deleted file mode 100644
index 3892d28..0000000
--- a/services/core/jni/com_android_server_BootReceiver.cpp
+++ /dev/null
@@ -1,57 +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.
- */
-
-#include <libdebuggerd/tombstone.h>
-#include <nativehelper/JNIHelp.h>
-
-#include <sstream>
-
-#include "jni.h"
-#include "tombstone.pb.h"
-
-namespace android {
-
-static void writeToString(std::stringstream& ss, const std::string& line, bool should_log) {
-    ss << line << std::endl;
-}
-
-static jstring com_android_server_BootReceiver_getTombstoneText(JNIEnv* env, jobject,
-                                                                jbyteArray tombstoneBytes) {
-    Tombstone tombstone;
-    tombstone.ParseFromArray(env->GetByteArrayElements(tombstoneBytes, 0),
-                             env->GetArrayLength(tombstoneBytes));
-
-    std::stringstream tombstoneString;
-
-    tombstone_proto_to_text(tombstone,
-                            std::bind(&writeToString, std::ref(tombstoneString),
-                                      std::placeholders::_1, std::placeholders::_2));
-
-    return env->NewStringUTF(tombstoneString.str().c_str());
-}
-
-static const JNINativeMethod sMethods[] = {
-        /* name, signature, funcPtr */
-        {"getTombstoneText", "([B)Ljava/lang/String;",
-         (jstring*)com_android_server_BootReceiver_getTombstoneText},
-};
-
-int register_com_android_server_BootReceiver(JNIEnv* env) {
-    return jniRegisterNativeMethods(env, "com/android/server/BootReceiver", sMethods,
-                                    NELEM(sMethods));
-}
-
-} // namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index deae82c..11734da 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -65,7 +65,6 @@
 int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env);
 int register_android_server_companion_virtual_InputController(JNIEnv* env);
 int register_android_server_app_GameManagerService(JNIEnv* env);
-int register_com_android_server_BootReceiver(JNIEnv* env);
 int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env);
 int register_com_android_server_display_DisplayControl(JNIEnv* env);
 int register_com_android_server_SystemClockTime(JNIEnv* env);
@@ -127,7 +126,6 @@
     register_android_server_sensor_SensorService(vm, env);
     register_android_server_companion_virtual_InputController(env);
     register_android_server_app_GameManagerService(env);
-    register_com_android_server_BootReceiver(env);
     register_com_android_server_wm_TaskFpsCallbackController(env);
     register_com_android_server_display_DisplayControl(env);
     register_com_android_server_SystemClockTime(env);
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index c625b1e..adbd3c9 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -595,7 +595,7 @@
             <!-- Sets the brightness mapping of the desired screen brightness to the corresponding
             lux for the current display -->
             <xs:element name="luxToBrightnessMapping" type="luxToBrightnessMapping"
-                        minOccurs="0" maxOccurs="1">
+                        minOccurs="0" maxOccurs="unbounded">
                 <xs:annotation name="final"/>
             </xs:element>
         </xs:sequence>
@@ -619,12 +619,20 @@
 
     This is used in place of config_autoBrightnessLevels and config_autoBrightnessLcdBacklightValues
     defined in the config XML resource.
+
+    On devices that allow users to choose from a set of predefined options in display
+    auto-brightness settings, multiple mappings for different modes and settings can be defined.
+
+    If no mode is specified, the mapping will be used for the default mode.
+    If no setting is specified, the mapping will be used for the normal brightness setting.
     -->
     <xs:complexType name="luxToBrightnessMapping">
         <xs:element name="map" type="nonNegativeFloatToFloatMap">
             <xs:annotation name="nonnull"/>
             <xs:annotation name="final"/>
         </xs:element>
+        <xs:element name="mode" type="xs:string" minOccurs="0"/>
+        <xs:element name="setting" type="xs:string" minOccurs="0"/>
     </xs:complexType>
 
     <!-- Represents a point in the display brightness mapping, representing the lux level from the
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 8c8c123..98c95ed 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -8,13 +8,12 @@
     method public final java.math.BigInteger getDarkeningLightDebounceIdleMillis();
     method public final java.math.BigInteger getDarkeningLightDebounceMillis();
     method public boolean getEnabled();
-    method public final com.android.server.display.config.LuxToBrightnessMapping getLuxToBrightnessMapping();
+    method public final java.util.List<com.android.server.display.config.LuxToBrightnessMapping> getLuxToBrightnessMapping();
     method public final void setBrighteningLightDebounceIdleMillis(java.math.BigInteger);
     method public final void setBrighteningLightDebounceMillis(java.math.BigInteger);
     method public final void setDarkeningLightDebounceIdleMillis(java.math.BigInteger);
     method public final void setDarkeningLightDebounceMillis(java.math.BigInteger);
     method public void setEnabled(boolean);
-    method public final void setLuxToBrightnessMapping(com.android.server.display.config.LuxToBrightnessMapping);
   }
 
   public class BlockingZoneConfig {
@@ -220,7 +219,11 @@
   public class LuxToBrightnessMapping {
     ctor public LuxToBrightnessMapping();
     method @NonNull public final com.android.server.display.config.NonNegativeFloatToFloatMap getMap();
+    method public String getMode();
+    method public String getSetting();
     method public final void setMap(@NonNull com.android.server.display.config.NonNegativeFloatToFloatMap);
+    method public void setMode(String);
+    method public void setSetting(String);
   }
 
   public class NitsMap {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index e0a2f30..a490013 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2469,7 +2469,7 @@
     private void migratePersonalAppSuspensionLocked(
             int doUserId, int poUserId, ActiveAdmin poAdmin) {
         final PackageManagerInternal pmi = mInjector.getPackageManagerInternal();
-        if (!pmi.isSuspendingAnyPackages(PLATFORM_PACKAGE_NAME, doUserId)) {
+        if (!pmi.isAdminSuspendingAnyPackages(doUserId)) {
             Slogf.i(LOG_TAG, "DO is not suspending any apps.");
             return;
         }
@@ -2480,7 +2480,7 @@
             poAdmin.mSuspendPersonalApps = true;
         } else {
             Slogf.i(LOG_TAG, "PO isn't targeting R+, unsuspending personal apps.");
-            pmi.unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, doUserId);
+            pmi.unsuspendAdminSuspendedPackages(doUserId);
         }
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index 6570ce1..506dbe8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -16,8 +16,6 @@
 
 package com.android.server.devicepolicy;
 
-import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppGlobals;
@@ -287,7 +285,7 @@
                 suspendPersonalAppsInPackageManager(context, userId);
             } else {
                 LocalServices.getService(PackageManagerInternal.class)
-                        .unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, userId);
+                        .unsuspendAdminSuspendedPackages(userId);
             }
         });
         return true;
diff --git a/services/manifest_services.xml b/services/manifest_services.xml
index eae159f..8ff1a7d 100644
--- a/services/manifest_services.xml
+++ b/services/manifest_services.xml
@@ -1,7 +1,7 @@
 <manifest version="1.0" type="framework">
     <hal format="aidl">
         <name>android.frameworks.location.altitude</name>
-        <version>1</version>
+        <version>2</version>
         <fqname>IAltitudeService/default</fqname>
     </hal>
     <hal format="aidl">
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index f69f628..022268d 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -1262,7 +1262,7 @@
         val apexModuleName = packageState.apexModuleName
         val packageName = packageState.packageName
         return when {
-            packageState.isVendor ->
+            packageState.isVendor || packageState.isOdm ->
                 permissionAllowlist.getVendorPrivilegedAppAllowlistState(
                     packageName,
                     permissionName
@@ -1471,12 +1471,15 @@
                     // In any case, don't grant a privileged permission to privileged vendor apps,
                     // if the permission's protectionLevel does not have the extra vendorPrivileged
                     // flag.
-                    if (packageState.isVendor && !permission.isVendorPrivileged) {
+                    if (
+                        (packageState.isVendor || packageState.isOdm) &&
+                            !permission.isVendorPrivileged
+                    ) {
                         Slog.w(
                             LOG_TAG,
                             "Permission $permissionName cannot be granted to privileged" +
-                                " vendor app $packageName because it isn't a vendorPrivileged" +
-                                " permission"
+                                " vendor (or odm) app $packageName because it isn't a" +
+                                " vendorPrivileged permission"
                         )
                         return false
                     }
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index 3d0b389..6fff012 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -36,6 +36,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -50,6 +51,7 @@
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.SuspendDialogInfo;
 import android.content.pm.UserInfo;
+import android.content.pm.UserPackage;
 import android.os.BaseBundle;
 import android.os.Message;
 import android.os.PersistableBundle;
@@ -57,6 +59,9 @@
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.flag.junit.SetFlagsRule;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
@@ -122,6 +127,8 @@
     private static final String PACKAGE_NAME_3 = "com.android.app3";
     private static final int TEST_RESOURCE_ID = 2131231283;
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
     @Mock
     RuntimePermissionsPersistence mRuntimePermissionsPersistence;
     @Mock
@@ -425,7 +432,7 @@
         PackageUserStateInternal packageUserState1 = ps1.readUserState(0);
         assertThat(packageUserState1.isSuspended(), is(true));
         assertThat(packageUserState1.getSuspendParams().size(), is(1));
-        assertThat(packageUserState1.getSuspendParams().keyAt(0), is("android"));
+        assertThat(packageUserState1.getSuspendParams().keyAt(0), is(UserPackage.of(0, "android")));
         assertThat(packageUserState1.getSuspendParams().valueAt(0).getAppExtras(), is(nullValue()));
         assertThat(packageUserState1.getSuspendParams().valueAt(0).getDialogInfo(),
                 is(nullValue()));
@@ -437,7 +444,7 @@
         packageUserState1 = ps1.readUserState(0);
         assertThat(packageUserState1.isSuspended(), is(true));
         assertThat(packageUserState1.getSuspendParams().size(), is(1));
-        assertThat(packageUserState1.getSuspendParams().keyAt(0), is("android"));
+        assertThat(packageUserState1.getSuspendParams().keyAt(0), is(UserPackage.of(0, "android")));
         assertThat(packageUserState1.getSuspendParams().valueAt(0).getAppExtras(), is(nullValue()));
         assertThat(packageUserState1.getSuspendParams().valueAt(0).getDialogInfo(),
                 is(nullValue()));
@@ -472,7 +479,8 @@
         watcher.verifyNoChangeReported("readUserState");
         assertThat(packageUserState1.isSuspended(), is(true));
         assertThat(packageUserState1.getSuspendParams().size(), is(1));
-        assertThat(packageUserState1.getSuspendParams().keyAt(0), is(PACKAGE_NAME_3));
+        assertThat(packageUserState1.getSuspendParams().keyAt(0),
+                is(UserPackage.of(0, PACKAGE_NAME_3)));
         final SuspendParams params = packageUserState1.getSuspendParams().valueAt(0);
         watcher.verifyNoChangeReported("fetch user state");
         assertThat(params, is(notNullValue()));
@@ -523,19 +531,24 @@
                 .setNeutralButtonAction(BUTTON_ACTION_UNSUSPEND)
                 .build();
 
-        ps1.modifyUserState(0).putSuspendParams("suspendingPackage1",
+        UserPackage suspender1 = UserPackage.of(0, "suspendingPackage1");
+        UserPackage suspender2 = UserPackage.of(0, "suspendingPackage2");
+        UserPackage suspender3 = UserPackage.of(0, "suspendingPackage3");
+        UserPackage irrelevantSuspender = UserPackage.of(0, "irrelevant");
+
+        ps1.modifyUserState(0).putSuspendParams(suspender1,
                 new SuspendParams(dialogInfo1, appExtras1, launcherExtras1));
-        ps1.modifyUserState(0).putSuspendParams("suspendingPackage2",
+        ps1.modifyUserState(0).putSuspendParams(suspender2,
                 new SuspendParams(dialogInfo2, appExtras2, launcherExtras2));
         settingsUnderTest.mPackages.put(PACKAGE_NAME_1, ps1);
         watcher.verifyChangeReported("put package 1");
 
-        ps2.modifyUserState(0).putSuspendParams("suspendingPackage3",
+        ps2.modifyUserState(0).putSuspendParams(suspender3,
                 new SuspendParams(null, appExtras1, null));
         settingsUnderTest.mPackages.put(PACKAGE_NAME_2, ps2);
         watcher.verifyChangeReported("put package 2");
 
-        ps3.modifyUserState(0).removeSuspension("irrelevant");
+        ps3.modifyUserState(0).removeSuspension(irrelevantSuspender);
         settingsUnderTest.mPackages.put(PACKAGE_NAME_3, ps3);
         watcher.verifyChangeReported("put package 3");
 
@@ -560,7 +573,7 @@
         assertThat(readPus1.getSuspendParams().size(), is(2));
         watcher.verifyNoChangeReported("read package param");
 
-        assertThat(readPus1.getSuspendParams().keyAt(0), is("suspendingPackage1"));
+        assertThat(readPus1.getSuspendParams().keyAt(0), is(suspender1));
         final SuspendParams params11 = readPus1.getSuspendParams().valueAt(0);
         watcher.verifyNoChangeReported("read package param");
         assertThat(params11, is(notNullValue()));
@@ -570,7 +583,7 @@
                 is(true));
         watcher.verifyNoChangeReported("read package param");
 
-        assertThat(readPus1.getSuspendParams().keyAt(1), is("suspendingPackage2"));
+        assertThat(readPus1.getSuspendParams().keyAt(1), is(suspender2));
         final SuspendParams params12 = readPus1.getSuspendParams().valueAt(1);
         assertThat(params12, is(notNullValue()));
         assertThat(params12.getDialogInfo(), is(dialogInfo2));
@@ -583,7 +596,7 @@
                 .readUserState(0);
         assertThat(readPus2.isSuspended(), is(true));
         assertThat(readPus2.getSuspendParams().size(), is(1));
-        assertThat(readPus2.getSuspendParams().keyAt(0), is("suspendingPackage3"));
+        assertThat(readPus2.getSuspendParams().keyAt(0), is(suspender3));
         final SuspendParams params21 = readPus2.getSuspendParams().valueAt(0);
         assertThat(params21, is(notNullValue()));
         assertThat(params21.getDialogInfo(), is(nullValue()));
@@ -1087,6 +1100,8 @@
             new File(InstrumentationRegistry.getContext().getFilesDir(), "com.android.bar-1");
     private static final File UPDATED_CODE_PATH =
             new File(InstrumentationRegistry.getContext().getFilesDir(), "com.android.bar-2");
+    private static final File UPDATED_CODE_PATH2 =
+            new File(InstrumentationRegistry.getContext().getFilesDir(), "com.android.bar-3");
     private static final long INITIAL_VERSION_CODE = 10023L;
     private static final long UPDATED_VERSION_CODE = 10025L;
 
@@ -1134,7 +1149,8 @@
                 .setNeutralButtonText(0x11220003)
                 .setNeutralButtonAction(BUTTON_ACTION_MORE_DETAILS)
                 .build();
-        origPkgSetting01.modifyUserState(0).putSuspendParams("suspendingPackage1",
+        origPkgSetting01.modifyUserState(0).putSuspendParams(
+                UserPackage.of(0, "suspendingPackage1"),
                 new SuspendParams(dialogInfo1, appExtras1, launcherExtras1));
         origPkgSetting01.setPkg(mockAndroidPackage(origPkgSetting01));
         final PackageSetting testPkgSetting01 = new PackageSetting(
@@ -1182,13 +1198,16 @@
                 null /*mimeGroups*/,
                 UUID.randomUUID(),
                 34 /*targetSdkVersion*/,
-                null /*restrictUpdateHash*/);
+                null /*restrictUpdateHash*/,
+                false /*isDontKill*/);
         assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
         assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a"));
         assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi"));
         assertThat(testPkgSetting01.getSecondaryCpuAbiLegacy(), is("armeabi"));
         assertThat(testPkgSetting01.getFlags(), is(0));
         assertThat(testPkgSetting01.getPrivateFlags(), is(0));
+        assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
+        assertNull(testPkgSetting01.getOldPaths());
         final PackageUserState userState = testPkgSetting01.readUserState(0);
         verifyUserState(userState, false /*notLaunched*/,
                 false /*stopped*/, false /*installed*/);
@@ -1223,7 +1242,8 @@
                 null /*mimeGroups*/,
                 UUID.randomUUID(),
                 34 /*targetSdkVersion*/,
-                null /*restrictUpdateHash*/);
+                null /*restrictUpdateHash*/,
+                false /*isDontKill*/);
         assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
         assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a"));
         assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi"));
@@ -1237,6 +1257,74 @@
                 false /*stopped*/, true /*installed*/);
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_IMPROVE_INSTALL_DONT_KILL)
+    public void testUpdatePackageSettings02WithOldPaths() throws PackageManagerException {
+        final PackageSetting testPkgSetting01 =
+                createPackageSetting(0 /*sharedUserId*/, 0 /*pkgFlags*/);
+        testPkgSetting01.setInstalled(false /*installed*/, 0 /*userId*/);
+        assertThat(testPkgSetting01.getFlags(), is(0));
+        assertThat(testPkgSetting01.getPrivateFlags(), is(0));
+        final PackageSetting oldPkgSetting01 = new PackageSetting(testPkgSetting01);
+        Settings.updatePackageSetting(
+                testPkgSetting01,
+                null /*disabledPkg*/,
+                null /*existingSharedUserSetting*/,
+                null /*sharedUser*/,
+                UPDATED_CODE_PATH /*codePath*/,
+                null /*legacyNativeLibraryPath*/,
+                "arm64-v8a" /*primaryCpuAbi*/,
+                "armeabi" /*secondaryCpuAbi*/,
+                ApplicationInfo.FLAG_SYSTEM /*pkgFlags*/,
+                ApplicationInfo.PRIVATE_FLAG_PRIVILEGED /*pkgPrivateFlags*/,
+                UserManagerService.getInstance(),
+                null /*usesSdkLibraries*/,
+                null /*usesSdkLibrariesVersions*/,
+                null /*usesSdkLibrariesOptional*/,
+                null /*usesStaticLibraries*/,
+                null /*usesStaticLibrariesVersions*/,
+                null /*mimeGroups*/,
+                UUID.randomUUID(),
+                34 /*targetSdkVersion*/,
+                null /*restrictUpdateHash*/,
+                true /*isDontKill*/);
+        assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
+        assertThat(testPkgSetting01.getOldPaths().size(), is(1));
+        assertTrue(testPkgSetting01.getOldPaths().contains(INITIAL_CODE_PATH));
+        Settings.updatePackageSetting(
+                testPkgSetting01,
+                null /*disabledPkg*/,
+                null /*existingSharedUserSetting*/,
+                null /*sharedUser*/,
+                UPDATED_CODE_PATH2 /*codePath*/,
+                null /*legacyNativeLibraryPath*/,
+                "arm64-v8a" /*primaryCpuAbi*/,
+                "armeabi" /*secondaryCpuAbi*/,
+                ApplicationInfo.FLAG_SYSTEM /*pkgFlags*/,
+                ApplicationInfo.PRIVATE_FLAG_PRIVILEGED /*pkgPrivateFlags*/,
+                UserManagerService.getInstance(),
+                null /*usesSdkLibraries*/,
+                null /*usesSdkLibrariesVersions*/,
+                null /*usesSdkLibrariesOptional*/,
+                null /*usesStaticLibraries*/,
+                null /*usesStaticLibrariesVersions*/,
+                null /*mimeGroups*/,
+                UUID.randomUUID(),
+                34 /*targetSdkVersion*/,
+                null /*restrictUpdateHash*/,
+                true /*isDontKill*/);
+        assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH2));
+        assertThat(testPkgSetting01.getOldPaths().size(), is(2));
+        int index = 0;
+        for (File path : testPkgSetting01.getOldPaths()) {
+            switch (index) {
+                case 0 -> assertThat(path, is(INITIAL_CODE_PATH));
+                case 1 -> assertThat(path, is(UPDATED_CODE_PATH));
+            }
+            index++;
+        }
+    }
+
     /** Update package; changing shared user throws exception */
     @Test
     public void testUpdatePackageSetting03() {
@@ -1266,7 +1354,8 @@
                     null /*mimeGroups*/,
                     UUID.randomUUID(),
                     34 /*targetSdkVersion*/,
-                    null /*restrictUpdateHash*/);
+                    null /*restrictUpdateHash*/,
+                    false /*isDontKill*/);
             fail("Expected a PackageManagerException");
         } catch (PackageManagerException expected) {
         }
@@ -1672,6 +1761,63 @@
         assertSame(sus1, settings.getSharedUserSettingLPr(ps1));
     }
 
+    @Test
+    public void testAddRemoveOldPaths() {
+        final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
+        ps1.addOldPath(INITIAL_CODE_PATH);
+        ps1.addOldPath(UPDATED_CODE_PATH);
+        ps1.addOldPath(UPDATED_CODE_PATH2);
+        assertThat(ps1.getOldPaths().size(), is(3));
+        int index = 0;
+        for (File path : ps1.getOldPaths()) {
+            switch (index) {
+                case 0 -> assertThat(path, is(INITIAL_CODE_PATH));
+                case 1 -> assertThat(path, is(UPDATED_CODE_PATH));
+                case 2 -> assertThat(path, is(UPDATED_CODE_PATH2));
+            }
+            index++;
+        }
+
+        ps1.removeOldPath(UPDATED_CODE_PATH);
+        assertThat(ps1.getOldPaths().size(), is(2));
+        index = 0;
+        for (File path : ps1.getOldPaths()) {
+            switch (index) {
+                case 0 -> assertThat(path, is(INITIAL_CODE_PATH));
+                case 1 -> assertThat(path, is(UPDATED_CODE_PATH2));
+            }
+            index++;
+        }
+        ps1.removeOldPath(UPDATED_CODE_PATH2);
+        // assert not throws when deleting non-existing element
+        ps1.removeOldPath(UPDATED_CODE_PATH2);
+        assertThat(ps1.getOldPaths().size(), is(1));
+        assertTrue(ps1.getOldPaths().contains(INITIAL_CODE_PATH));
+        ps1.removeOldPath(INITIAL_CODE_PATH);
+        // assert not throws when deleting from an empty list
+        ps1.removeOldPath(INITIAL_CODE_PATH);
+        assertThat(ps1.getOldPaths().size(), is(0));
+    }
+
+    @Test
+    public void testOldPathsNotPreservedAfterReboot() {
+        Settings settings = makeSettings();
+        final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
+        ps1.setAppId(Process.FIRST_APPLICATION_UID);
+        ps1.setPkg(PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed()
+                .setUid(ps1.getAppId())
+                .hideAsFinal());
+        ps1.addOldPath(INITIAL_CODE_PATH);
+        ps1.addOldPath(UPDATED_CODE_PATH);
+        settings.mPackages.put(PACKAGE_NAME_1, ps1);
+
+        settings.writeLPr(computer, /*sync=*/true);
+        settings.mPackages.clear();
+
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+        assertNull(settings.getPackageLPr(PACKAGE_NAME_1).getOldPaths());
+    }
+
     private void verifyUserState(PackageUserState userState,
             boolean notLaunched, boolean stopped, boolean installed) {
         assertThat(userState.getEnabledState(), is(0));
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
index c0c7032..9780440 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
@@ -27,6 +27,7 @@
 import android.content.ComponentName;
 import android.content.pm.PackageManager;
 import android.content.pm.SuspendDialogInfo;
+import android.content.pm.UserPackage;
 import android.content.pm.overlay.OverlayPaths;
 import android.os.PersistableBundle;
 import android.platform.test.annotations.Presubmit;
@@ -89,7 +90,7 @@
         assertThat(testUserState.equals(oldUserState), is(false));
 
         oldUserState = new PackageUserStateImpl();
-        oldUserState.putSuspendParams("suspendingPackage",
+        oldUserState.putSuspendParams(UserPackage.of(0, "suspendingPackage"),
                 new SuspendParams(null, new PersistableBundle(), null));
         assertThat(testUserState.equals(oldUserState), is(false));
 
@@ -220,6 +221,8 @@
         final PersistableBundle launcherExtras2 = createPersistableBundle(null, 0, "name",
                 "launcherExtras2", null, 0);
 
+        final int suspendingUser1 = 0;
+        final int suspendingUser2 = 10;
         final String suspendingPackage1 = "package1";
         final String suspendingPackage2 = "package2";
 
@@ -230,12 +233,12 @@
                 .setMessage("dialogMessage2")
                 .build();
 
-        final ArrayMap<String, SuspendParams> paramsMap1 = new ArrayMap<>();
-        paramsMap1.put(suspendingPackage1, createSuspendParams(dialogInfo1, appExtras1,
-                launcherExtras1));
-        final ArrayMap<String, SuspendParams> paramsMap2 = new ArrayMap<>();
-        paramsMap2.put(suspendingPackage2, createSuspendParams(dialogInfo2,
-                appExtras2, launcherExtras2));
+        final ArrayMap<UserPackage, SuspendParams> paramsMap1 = new ArrayMap<>();
+        paramsMap1.put(UserPackage.of(suspendingUser1, suspendingPackage1),
+                createSuspendParams(dialogInfo1, appExtras1, launcherExtras1));
+        final ArrayMap<UserPackage, SuspendParams> paramsMap2 = new ArrayMap<>();
+        paramsMap2.put(UserPackage.of(suspendingUser2, suspendingPackage2),
+                createSuspendParams(dialogInfo2, appExtras2, launcherExtras2));
 
 
         final PackageUserStateImpl testUserState1 = new PackageUserStateImpl();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 2bdebe2..418b78c 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -17,6 +17,8 @@
 package com.android.server.display;
 
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -39,7 +41,9 @@
 import android.hardware.SensorManager;
 import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
 import android.os.Handler;
+import android.os.PowerManager;
 import android.os.test.TestLooper;
+import android.util.SparseArray;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -98,8 +102,8 @@
 
         mLightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor");
         mContext = InstrumentationRegistry.getContext();
-        setupController(BrightnessMappingStrategy.NO_USER_LUX,
-                BrightnessMappingStrategy.NO_USER_BRIGHTNESS, /* applyDebounce= */ false,
+        setupController(BrightnessMappingStrategy.INVALID_LUX,
+                BrightnessMappingStrategy.INVALID_NITS, /* applyDebounce= */ false,
                 /* useHorizon= */ true);
     }
 
@@ -112,11 +116,19 @@
         }
     }
 
-    private void setupController(float userLux, float userBrightness, boolean applyDebounce,
+    private void setupController(float userLux, float userNits, boolean applyDebounce,
             boolean useHorizon) {
         mClock = new OffsettableClock.Stopped();
         mTestLooper = new TestLooper(mClock::now);
 
+        when(mBrightnessMappingStrategy.getMode()).thenReturn(AUTO_BRIGHTNESS_MODE_DEFAULT);
+        when(mIdleBrightnessMappingStrategy.getMode()).thenReturn(AUTO_BRIGHTNESS_MODE_IDLE);
+
+        SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap = new SparseArray<>();
+        brightnessMappingStrategyMap.append(AUTO_BRIGHTNESS_MODE_DEFAULT,
+                mBrightnessMappingStrategy);
+        brightnessMappingStrategyMap.append(AUTO_BRIGHTNESS_MODE_IDLE,
+                mIdleBrightnessMappingStrategy);
         mController = new AutomaticBrightnessController(
                 new AutomaticBrightnessController.Injector() {
                     @Override
@@ -131,7 +143,7 @@
 
                 }, // pass in test looper instead, pass in offsettable clock
                 () -> { }, mTestLooper.getLooper(), mSensorManager, mLightSensor,
-                mBrightnessMappingStrategy, LIGHT_SENSOR_WARMUP_TIME, BRIGHTNESS_MIN_FLOAT,
+                brightnessMappingStrategyMap, LIGHT_SENSOR_WARMUP_TIME, BRIGHTNESS_MIN_FLOAT,
                 BRIGHTNESS_MAX_FLOAT, DOZE_SCALE_FACTOR, LIGHT_SENSOR_RATE,
                 INITIAL_LIGHT_SENSOR_RATE, applyDebounce ? BRIGHTENING_LIGHT_DEBOUNCE_CONFIG : 0,
                 applyDebounce ? DARKENING_LIGHT_DEBOUNCE_CONFIG : 0,
@@ -141,8 +153,8 @@
                 mAmbientBrightnessThresholds, mScreenBrightnessThresholds,
                 mAmbientBrightnessThresholdsIdle, mScreenBrightnessThresholdsIdle,
                 mContext, mBrightnessRangeController, mBrightnessThrottler,
-                mIdleBrightnessMappingStrategy, useHorizon ? AMBIENT_LIGHT_HORIZON_SHORT : 1,
-                useHorizon ? AMBIENT_LIGHT_HORIZON_LONG : 10000, userLux, userBrightness
+                useHorizon ? AMBIENT_LIGHT_HORIZON_SHORT : 1,
+                useHorizon ? AMBIENT_LIGHT_HORIZON_LONG : 10000, userLux, userNits
         );
 
         when(mBrightnessRangeController.getCurrentBrightnessMax()).thenReturn(
@@ -326,8 +338,7 @@
 
         when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);
 
-        mController.switchToIdleMode();
-        when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true);
+        mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE);
         when(mBrightnessMappingStrategy.shouldResetShortTermModel(
                 123f, 0.5f)).thenReturn(true);
 
@@ -337,7 +348,7 @@
                 mBrightnessMappingStrategy.getShortTermModelTimeout() + 1000);
         mTestLooper.dispatchAll();
 
-        mController.switchToInteractiveScreenBrightnessMode();
+        mController.switchMode(AUTO_BRIGHTNESS_MODE_DEFAULT);
         mTestLooper.moveTimeForward(4000);
         mTestLooper.dispatchAll();
 
@@ -371,15 +382,14 @@
         when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(0.51f);
         when(mBrightnessMappingStrategy.getUserLux()).thenReturn(123.0f);
 
-        mController.switchToIdleMode();
-        when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true);
+        mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE);
 
         // Time does not move forward, since clock is doesn't increment naturally.
         mTestLooper.dispatchAll();
 
         // Sensor reads 100000 lux,
         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 678910));
-        mController.switchToInteractiveScreenBrightnessMode();
+        mController.switchMode(AUTO_BRIGHTNESS_MODE_DEFAULT);
 
         // Verify short term model is not reset.
         verify(mBrightnessMappingStrategy, never()).clearUserDataPoints();
@@ -410,10 +420,11 @@
         when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(0.5f);
         when(mBrightnessMappingStrategy.getUserLux()).thenReturn(123f);
 
-        mController.switchToIdleMode();
-        when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true);
-        when(mIdleBrightnessMappingStrategy.getUserBrightness()).thenReturn(-1f);
-        when(mIdleBrightnessMappingStrategy.getUserLux()).thenReturn(-1f);
+        mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE);
+        when(mIdleBrightnessMappingStrategy.getUserBrightness()).thenReturn(
+                PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        when(mIdleBrightnessMappingStrategy.getUserLux()).thenReturn(
+                BrightnessMappingStrategy.INVALID_LUX);
         when(mBrightnessMappingStrategy.shouldResetShortTermModel(
                 123f, 0.5f)).thenReturn(true);
 
@@ -423,7 +434,7 @@
                 mBrightnessMappingStrategy.getShortTermModelTimeout() + 1000);
         mTestLooper.dispatchAll();
 
-        mController.switchToInteractiveScreenBrightnessMode();
+        mController.switchMode(AUTO_BRIGHTNESS_MODE_DEFAULT);
         mTestLooper.moveTimeForward(4000);
         mTestLooper.dispatchAll();
 
@@ -456,10 +467,11 @@
         when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(0.5f);
         when(mBrightnessMappingStrategy.getUserLux()).thenReturn(123f);
 
-        mController.switchToIdleMode();
-        when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true);
-        when(mIdleBrightnessMappingStrategy.getUserBrightness()).thenReturn(-1f);
-        when(mIdleBrightnessMappingStrategy.getUserLux()).thenReturn(-1f);
+        mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE);
+        when(mIdleBrightnessMappingStrategy.getUserBrightness()).thenReturn(
+                PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        when(mIdleBrightnessMappingStrategy.getUserLux()).thenReturn(
+                BrightnessMappingStrategy.INVALID_LUX);
 
         when(mBrightnessMappingStrategy.shouldResetShortTermModel(
                 123f, 0.5f)).thenReturn(true);
@@ -469,7 +481,7 @@
         // Do not fast-forward time.
         mTestLooper.dispatchAll();
 
-        mController.switchToInteractiveScreenBrightnessMode();
+        mController.switchMode(AUTO_BRIGHTNESS_MODE_DEFAULT);
         // Do not fast-forward time
         mTestLooper.dispatchAll();
 
@@ -492,22 +504,25 @@
         // Sensor reads 123 lux,
         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
         when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);
-        when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(-1.0f);
-        when(mBrightnessMappingStrategy.getUserLux()).thenReturn(-1.0f);
+        when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(
+                PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        when(mBrightnessMappingStrategy.getUserLux()).thenReturn(
+                BrightnessMappingStrategy.INVALID_LUX);
 
         // No user brightness interaction.
 
-        mController.switchToIdleMode();
-        when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true);
-        when(mIdleBrightnessMappingStrategy.getUserBrightness()).thenReturn(-1.0f);
-        when(mIdleBrightnessMappingStrategy.getUserLux()).thenReturn(-1.0f);
+        mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE);
+        when(mIdleBrightnessMappingStrategy.getUserBrightness()).thenReturn(
+                PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        when(mIdleBrightnessMappingStrategy.getUserLux()).thenReturn(
+                BrightnessMappingStrategy.INVALID_LUX);
 
         // Sensor reads 1000 lux,
         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
         // Do not fast-forward time.
         mTestLooper.dispatchAll();
 
-        mController.switchToInteractiveScreenBrightnessMode();
+        mController.switchMode(AUTO_BRIGHTNESS_MODE_DEFAULT);
         // Do not fast-forward time
         mTestLooper.dispatchAll();
 
@@ -541,12 +556,12 @@
         verify(mBrightnessMappingStrategy, times(3)).getBrightness(anyFloat(), any(), anyInt());
 
         // Now let's do the same for idle mode
-        mController.switchToIdleMode();
-        // Called once for init, and once when switching,
+        mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE);
+        // Called once when switching,
         // setAmbientLux() is called twice and once in updateAutoBrightness(),
         // nextAmbientLightBrighteningTransition() and nextAmbientLightDarkeningTransition() are
         // called twice each.
-        verify(mBrightnessMappingStrategy, times(9)).isForIdleMode();
+        verify(mBrightnessMappingStrategy, times(8)).getMode();
         // Called when switching.
         verify(mBrightnessMappingStrategy, times(1)).getShortTermModelTimeout();
         verify(mBrightnessMappingStrategy, times(1)).getUserBrightness();
@@ -826,7 +841,6 @@
 
     @Test
     public void testResetShortTermModelWhenConfigChanges() {
-        when(mBrightnessMappingStrategy.isForIdleMode()).thenReturn(false);
         when(mBrightnessMappingStrategy.setBrightnessConfiguration(any())).thenReturn(true);
 
         mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
@@ -847,8 +861,10 @@
         verify(mBrightnessMappingStrategy, never()).addUserDataPoint(anyFloat(), anyFloat());
 
         float userLux = 1000;
+        float userNits = 500;
         float userBrightness = 0.3f;
-        setupController(userLux, userBrightness, /* applyDebounce= */ true,
+        when(mBrightnessMappingStrategy.getBrightnessFromNits(userNits)).thenReturn(userBrightness);
+        setupController(userLux, userNits, /* applyDebounce= */ true,
                 /* useHorizon= */ false);
         verify(mBrightnessMappingStrategy).addUserDataPoint(userLux, userBrightness);
     }
@@ -856,8 +872,8 @@
     @Test
     public void testBrighteningLightDebounce() throws Exception {
         clearInvocations(mSensorManager);
-        setupController(BrightnessMappingStrategy.NO_USER_LUX,
-                BrightnessMappingStrategy.NO_USER_BRIGHTNESS, /* applyDebounce= */ true,
+        setupController(BrightnessMappingStrategy.INVALID_LUX,
+                BrightnessMappingStrategy.INVALID_NITS, /* applyDebounce= */ true,
                 /* useHorizon= */ false);
 
         ArgumentCaptor<SensorEventListener> listenerCaptor =
@@ -897,8 +913,8 @@
                 .thenReturn(10000f);
         when(mAmbientBrightnessThresholds.getDarkeningThreshold(anyFloat()))
                 .thenReturn(10000f);
-        setupController(BrightnessMappingStrategy.NO_USER_LUX,
-                BrightnessMappingStrategy.NO_USER_BRIGHTNESS, /* applyDebounce= */ true,
+        setupController(BrightnessMappingStrategy.INVALID_LUX,
+                BrightnessMappingStrategy.INVALID_NITS, /* applyDebounce= */ true,
                 /* useHorizon= */ false);
 
         ArgumentCaptor<SensorEventListener> listenerCaptor =
@@ -934,12 +950,11 @@
     @Test
     public void testBrighteningLightDebounceIdle() throws Exception {
         clearInvocations(mSensorManager);
-        setupController(BrightnessMappingStrategy.NO_USER_LUX,
-                BrightnessMappingStrategy.NO_USER_BRIGHTNESS, /* applyDebounce= */ true,
+        setupController(BrightnessMappingStrategy.INVALID_LUX,
+                BrightnessMappingStrategy.INVALID_NITS, /* applyDebounce= */ true,
                 /* useHorizon= */ false);
 
-        mController.switchToIdleMode();
-        when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true);
+        mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE);
 
         ArgumentCaptor<SensorEventListener> listenerCaptor =
                 ArgumentCaptor.forClass(SensorEventListener.class);
@@ -972,12 +987,11 @@
                 .thenReturn(10000f);
         when(mAmbientBrightnessThresholdsIdle.getDarkeningThreshold(anyFloat()))
                 .thenReturn(10000f);
-        setupController(BrightnessMappingStrategy.NO_USER_LUX,
-                BrightnessMappingStrategy.NO_USER_BRIGHTNESS, /* applyDebounce= */ true,
+        setupController(BrightnessMappingStrategy.INVALID_LUX,
+                BrightnessMappingStrategy.INVALID_NITS, /* applyDebounce= */ true,
                 /* useHorizon= */ false);
 
-        mController.switchToIdleMode();
-        when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true);
+        mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE);
 
         ArgumentCaptor<SensorEventListener> listenerCaptor =
                 ArgumentCaptor.forClass(SensorEventListener.class);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
index a2e80f0..c5a1ba1 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
@@ -16,6 +16,9 @@
 
 package com.android.server.display;
 
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
@@ -104,20 +107,6 @@
         468.5f,
     };
 
-    private static final int[] DISPLAY_LEVELS_INT = {
-        9,
-        30,
-        45,
-        62,
-        78,
-        96,
-        119,
-        146,
-        178,
-        221,
-        255
-    };
-
     private static final float[] DISPLAY_LEVELS = {
         0.03f,
         0.11f,
@@ -169,54 +158,23 @@
     DisplayWhiteBalanceController mMockDwbc;
 
     @Test
-    public void testSimpleStrategyMappingAtControlPoints_IntConfig() {
-        Resources res = createResources(DISPLAY_LEVELS_INT);
-        DisplayDeviceConfig ddc = createDdc();
-        BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+    public void testSimpleStrategyMappingAtControlPoints() {
+        Resources res = createResources();
+        DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS).build();
+        BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
         assertNotNull("BrightnessMappingStrategy should not be null", simple);
         for (int i = 0; i < LUX_LEVELS.length; i++) {
-            final float expectedLevel = MathUtils.map(PowerManager.BRIGHTNESS_OFF + 1,
-                    PowerManager.BRIGHTNESS_ON, PowerManager.BRIGHTNESS_MIN,
-                    PowerManager.BRIGHTNESS_MAX, DISPLAY_LEVELS_INT[i]);
-            assertEquals(expectedLevel,
-                    simple.getBrightness(LUX_LEVELS[i]), 0.0001f /*tolerance*/);
+            assertEquals(DISPLAY_LEVELS[i], simple.getBrightness(LUX_LEVELS[i]), TOLERANCE);
         }
     }
 
     @Test
-    public void testSimpleStrategyMappingBetweenControlPoints_IntConfig() {
-        Resources res = createResources(DISPLAY_LEVELS_INT);
-        DisplayDeviceConfig ddc = createDdc();
-        BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-        assertNotNull("BrightnessMappingStrategy should not be null", simple);
-        for (int i = 1; i < LUX_LEVELS.length; i++) {
-            final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2;
-            final float backlight = simple.getBrightness(lux) * PowerManager.BRIGHTNESS_ON;
-            assertTrue("Desired brightness should be between adjacent control points.",
-                    backlight > DISPLAY_LEVELS_INT[i - 1]
-                            && backlight < DISPLAY_LEVELS_INT[i]);
-        }
-    }
-
-    @Test
-    public void testSimpleStrategyMappingAtControlPoints_FloatConfig() {
-        Resources res = createResources(EMPTY_INT_ARRAY);
-        DisplayDeviceConfig ddc = createDdc(EMPTY_FLOAT_ARRAY, EMPTY_FLOAT_ARRAY, LUX_LEVELS,
-                EMPTY_FLOAT_ARRAY, DISPLAY_LEVELS);
-        BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-        assertNotNull("BrightnessMappingStrategy should not be null", simple);
-        for (int i = 0; i < LUX_LEVELS.length; i++) {
-            assertEquals(DISPLAY_LEVELS[i], simple.getBrightness(LUX_LEVELS[i]),
-                    /* tolerance= */ 0.0001f);
-        }
-    }
-
-    @Test
-    public void testSimpleStrategyMappingBetweenControlPoints_FloatConfig() {
-        Resources res = createResources(EMPTY_INT_ARRAY);
-        DisplayDeviceConfig ddc = createDdc(EMPTY_FLOAT_ARRAY, EMPTY_FLOAT_ARRAY, LUX_LEVELS,
-                EMPTY_FLOAT_ARRAY, DISPLAY_LEVELS);
-        BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+    public void testSimpleStrategyMappingBetweenControlPoints() {
+        Resources res = createResources();
+        DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS).build();
+        BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
         assertNotNull("BrightnessMappingStrategy should not be null", simple);
         for (int i = 1; i < LUX_LEVELS.length; i++) {
             final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2;
@@ -228,9 +186,10 @@
 
     @Test
     public void testSimpleStrategyIgnoresNewConfiguration() {
-        Resources res = createResources(DISPLAY_LEVELS_INT);
-        DisplayDeviceConfig ddc = createDdc();
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        Resources res = createResources();
+        DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS).build();
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
 
         final float[] lux = { 0f, 1f };
         final float[] nits = { 0, PowerManager.BRIGHTNESS_ON };
@@ -243,25 +202,25 @@
 
     @Test
     public void testSimpleStrategyIgnoresNullConfiguration() {
-        Resources res = createResources(DISPLAY_LEVELS_INT);
-        DisplayDeviceConfig ddc = createDdc();
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        Resources res = createResources();
+        DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS).build();
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
 
         strategy.setBrightnessConfiguration(null);
-        final int n = DISPLAY_LEVELS_INT.length;
-        final float expectedBrightness =
-                (float) DISPLAY_LEVELS_INT[n - 1] / PowerManager.BRIGHTNESS_ON;
+        final int n = DISPLAY_LEVELS.length;
+        final float expectedBrightness = DISPLAY_LEVELS[n - 1];
         assertEquals(expectedBrightness,
                 strategy.getBrightness(LUX_LEVELS[n - 1]), 0.0001f /*tolerance*/);
     }
 
     @Test
     public void testPhysicalStrategyMappingAtControlPoints() {
-        Resources res = createResources(EMPTY_INT_ARRAY);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT,
-                LUX_LEVELS, DISPLAY_LEVELS_NITS);
-        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        Resources res = createResources();
+        DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS)
+                .build();
+        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
         assertNotNull("BrightnessMappingStrategy should not be null", physical);
         for (int i = 0; i < LUX_LEVELS.length; i++) {
             final float expectedLevel = MathUtils.map(DISPLAY_RANGE_NITS[0], DISPLAY_RANGE_NITS[1],
@@ -276,10 +235,11 @@
 
     @Test
     public void testPhysicalStrategyMappingBetweenControlPoints() {
-        Resources res = createResources(EMPTY_INT_ARRAY);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE,
-                LUX_LEVELS, DISPLAY_LEVELS_NITS);
-        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        Resources res = createResources();
+        DisplayDeviceConfig ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE)
+                .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build();
+        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
         assertNotNull("BrightnessMappingStrategy should not be null", physical);
         Spline brightnessToNits =
                 Spline.createSpline(BACKLIGHT_RANGE_ZERO_TO_ONE, DISPLAY_RANGE_NITS);
@@ -294,10 +254,11 @@
 
     @Test
     public void testPhysicalStrategyUsesNewConfigurations() {
-        Resources res = createResources(EMPTY_INT_ARRAY);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS);
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        Resources res = createResources();
+        DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS)
+                .build();
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
 
         final float[] lux = {0f, 1f};
         final float[] nits = {
@@ -320,10 +281,11 @@
 
     @Test
     public void testPhysicalStrategyRecalculateSplines() {
-        Resources res = createResources(EMPTY_INT_ARRAY);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS);
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        Resources res = createResources();
+        DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS)
+                .build();
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
         float[] adjustedNits50p = new float[DISPLAY_RANGE_NITS.length];
         for (int i = 0; i < DISPLAY_RANGE_NITS.length; i++) {
             adjustedNits50p[i] = DISPLAY_RANGE_NITS[i] * 0.5f;
@@ -364,10 +326,12 @@
 
     @Test
     public void testDefaultStrategyIsPhysical() {
-        Resources res = createResources(DISPLAY_LEVELS_INT);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS);
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        Resources res = createResources();
+        DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS)
+                .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS)
+                .build();
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
         assertTrue(strategy instanceof BrightnessMappingStrategy.PhysicalMappingStrategy);
     }
 
@@ -378,17 +342,19 @@
         float tmp = lux[idx];
         lux[idx] = lux[idx + 1];
         lux[idx + 1] = tmp;
-        Resources res = createResources(EMPTY_INT_ARRAY);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, lux, DISPLAY_LEVELS_NITS);
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        Resources res = createResources();
+        DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(lux)
+                .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build();
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
         assertNull(strategy);
 
         // And make sure we get the same result even if it's monotone but not increasing.
         lux[idx] = lux[idx + 1];
-        ddc = createDdc(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, lux,
-                DISPLAY_LEVELS_NITS);
-        strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        ddc = new DdcBuilder().setAutoBrightnessLevelsLux(lux)
+                .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build();
+        strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
+                mMockDwbc);
         assertNull(strategy);
     }
 
@@ -399,71 +365,73 @@
         // Make sure it's strictly increasing so that the only failure is the differing array
         // lengths
         lux[lux.length - 1] = lux[lux.length - 2] + 1;
-        Resources res = createResources(EMPTY_INT_ARRAY);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, lux, DISPLAY_LEVELS_NITS);
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        Resources res = createResources();
+        DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(lux)
+                .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build();
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
         assertNull(strategy);
 
-        res = createResources(DISPLAY_LEVELS_INT);
-        strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        ddc = new DdcBuilder().setAutoBrightnessLevelsLux(lux)
+                .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS)
+                .setAutoBrightnessLevels(DISPLAY_LEVELS).build();
+        strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
+                mMockDwbc);
         assertNull(strategy);
 
         // Extra backlight level
-        final int[] backlight = Arrays.copyOf(
-                DISPLAY_LEVELS_INT, DISPLAY_LEVELS_INT.length + 1);
+        final float[] backlight = Arrays.copyOf(DISPLAY_LEVELS, DISPLAY_LEVELS.length + 1);
         backlight[backlight.length - 1] = backlight[backlight.length - 2] + 1;
-        res = createResources(backlight);
-        ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, EMPTY_FLOAT_ARRAY);
-        strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        res = createResources();
+        ddc = new DdcBuilder().setAutoBrightnessLevels(backlight).build();
+        strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
+                mMockDwbc);
         assertNull(strategy);
 
         // Extra nits level
         final float[] nits = Arrays.copyOf(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_NITS.length + 1);
         nits[nits.length - 1] = nits[nits.length - 2] + 1;
-        res = createResources(EMPTY_INT_ARRAY);
-        ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, nits);
-        strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        res = createResources();
+        ddc = new DdcBuilder().setAutoBrightnessLevelsNits(nits)
+                .setAutoBrightnessLevels(EMPTY_FLOAT_ARRAY).build();
+        strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
+                mMockDwbc);
         assertNull(strategy);
     }
 
     @Test
     public void testPhysicalStrategyRequiresNitsMapping() {
-        Resources res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
-        DisplayDeviceConfig ddc = createDdc(EMPTY_FLOAT_ARRAY /*nitsRange*/);
-        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-        assertNull(physical);
-
-        res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
-        physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-        assertNull(physical);
-
-        res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
-        physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        Resources res = createResources();
+        DisplayDeviceConfig ddc = new DdcBuilder().setNitsRange(EMPTY_FLOAT_ARRAY).build();
+        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
         assertNull(physical);
     }
 
     @Test
     public void testStrategiesAdaptToUserDataPoint() {
-        Resources res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE,
-                LUX_LEVELS, DISPLAY_LEVELS_NITS);
-        assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc, mMockDwbc));
-        ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE);
-        res = createResources(DISPLAY_LEVELS_INT);
-        assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc, mMockDwbc));
+        Resources res = createResources();
+        DisplayDeviceConfig ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE)
+                .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build();
+        assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc));
+        ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE)
+                .setAutoBrightnessLevels(DISPLAY_LEVELS).build();
+        res = createResources();
+        assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc));
     }
 
     @Test
     public void testIdleModeConfigLoadsCorrectly() {
-        Resources res = createResourcesIdle(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE);
+        Resources res = createResources(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE);
+        DisplayDeviceConfig ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE)
+                .build();
 
         // Create an idle mode bms
         // This will fail if it tries to fetch the wrong configuration.
-        BrightnessMappingStrategy bms = BrightnessMappingStrategy.createForIdleMode(res, ddc,
+        BrightnessMappingStrategy bms = BrightnessMappingStrategy.create(res, ddc,
+                AUTO_BRIGHTNESS_MODE_IDLE,
                 mMockDwbc);
         assertNotNull("BrightnessMappingStrategy should not be null", bms);
 
@@ -532,17 +500,11 @@
         assertEquals(minBrightness, strategy.getBrightness(LUX_LEVELS[0]), 0.0001f /*tolerance*/);
     }
 
-    private Resources createResources(int[] brightnessLevelsBacklight) {
-        return createResources(brightnessLevelsBacklight, EMPTY_INT_ARRAY, EMPTY_FLOAT_ARRAY);
+    private Resources createResources() {
+        return createResources(EMPTY_INT_ARRAY, EMPTY_FLOAT_ARRAY);
     }
 
-    private Resources createResourcesIdle(int[] luxLevelsIdle, float[] brightnessLevelsNitsIdle) {
-        return createResources(EMPTY_INT_ARRAY,
-                luxLevelsIdle, brightnessLevelsNitsIdle);
-    }
-
-    private Resources createResources(int[] brightnessLevelsBacklight, int[] luxLevelsIdle,
-            float[] brightnessLevelsNitsIdle) {
+    private Resources createResources(int[] luxLevelsIdle, float[] brightnessLevelsNitsIdle) {
 
         Resources mockResources = mock(Resources.class);
         if (luxLevelsIdle.length > 0) {
@@ -553,10 +515,6 @@
                     .thenReturn(luxLevelsIdleResource);
         }
 
-        when(mockResources.getIntArray(
-                com.android.internal.R.array.config_autoBrightnessLcdBacklightValues))
-                .thenReturn(brightnessLevelsBacklight);
-
         TypedArray mockBrightnessLevelNitsIdle = createFloatTypedArray(brightnessLevelsNitsIdle);
         when(mockResources.obtainTypedArray(
                 com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle))
@@ -574,41 +532,6 @@
         return mockResources;
     }
 
-    private DisplayDeviceConfig createDdc() {
-        return createDdc(DISPLAY_RANGE_NITS);
-    }
-
-    private DisplayDeviceConfig createDdc(float[] nitsArray) {
-        return createDdc(nitsArray, DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT);
-    }
-
-    private DisplayDeviceConfig createDdc(float[] nitsArray, float[] backlightArray) {
-        DisplayDeviceConfig mockDdc = mock(DisplayDeviceConfig.class);
-        when(mockDdc.getNits()).thenReturn(nitsArray);
-        when(mockDdc.getBrightness()).thenReturn(backlightArray);
-        when(mockDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(LUX_LEVELS);
-        when(mockDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(EMPTY_FLOAT_ARRAY);
-        when(mockDdc.getAutoBrightnessBrighteningLevels()).thenReturn(EMPTY_FLOAT_ARRAY);
-        return mockDdc;
-    }
-
-    private DisplayDeviceConfig createDdc(float[] nitsArray, float[] backlightArray,
-            float[] luxLevelsFloat, float[] brightnessLevelsNits) {
-        return createDdc(nitsArray, backlightArray, luxLevelsFloat, brightnessLevelsNits,
-                EMPTY_FLOAT_ARRAY);
-    }
-
-    private DisplayDeviceConfig createDdc(float[] nitsArray, float[] backlightArray,
-            float[] luxLevelsFloat, float[] brightnessLevelsNits, float[] brightnessLevels) {
-        DisplayDeviceConfig mockDdc = mock(DisplayDeviceConfig.class);
-        when(mockDdc.getNits()).thenReturn(nitsArray);
-        when(mockDdc.getBrightness()).thenReturn(backlightArray);
-        when(mockDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(luxLevelsFloat);
-        when(mockDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(brightnessLevelsNits);
-        when(mockDdc.getAutoBrightnessBrighteningLevels()).thenReturn(brightnessLevels);
-        return mockDdc;
-    }
-
     private TypedArray createFloatTypedArray(float[] vals) {
         TypedArray mockArray = mock(TypedArray.class);
         when(mockArray.length()).thenAnswer(invocation -> {
@@ -647,12 +570,11 @@
         final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
         final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3);
 
-        Resources resources = createResources(EMPTY_INT_ARRAY);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX,
-                GAMMA_CORRECTION_NITS);
+        Resources resources = createResources();
+        DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(GAMMA_CORRECTION_LUX)
+                .setAutoBrightnessLevelsNits(GAMMA_CORRECTION_NITS).build();
         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
-                mMockDwbc);
+                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
         // Let's start with a validity check:
         assertEquals(y1, strategy.getBrightness(x1), 0.0001f /* tolerance */);
         assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
@@ -678,12 +600,11 @@
         final float y1 = GAMMA_CORRECTION_SPLINE.interpolate(x1);
         final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
         final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3);
-        Resources resources = createResources(EMPTY_INT_ARRAY);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX,
-                GAMMA_CORRECTION_NITS);
+        Resources resources = createResources();
+        DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(GAMMA_CORRECTION_LUX)
+                .setAutoBrightnessLevelsNits(GAMMA_CORRECTION_NITS).build();
         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
-                mMockDwbc);
+                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
         // Validity check:
         assertEquals(y1, strategy.getBrightness(x1), 0.0001f /* tolerance */);
         assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
@@ -706,12 +627,11 @@
     public void testGammaCorrectionExtremeChangeAtCenter() {
         // Extreme changes (e.g. setting brightness to 0.0 or 1.0) can't be gamma corrected, so we
         // just make sure the adjustment reflects the change.
-        Resources resources = createResources(EMPTY_INT_ARRAY);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX,
-                GAMMA_CORRECTION_NITS);
+        Resources resources = createResources();
+        DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(GAMMA_CORRECTION_LUX)
+                .setAutoBrightnessLevelsNits(GAMMA_CORRECTION_NITS).build();
         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
-                mMockDwbc);
+                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
         assertEquals(0.0f, strategy.getAutoBrightnessAdjustment(), /* delta= */ 0.0001f);
         strategy.addUserDataPoint(/* lux= */ 2500, /* brightness= */ 1.0f);
         assertEquals(+1.0f, strategy.getAutoBrightnessAdjustment(), /* delta= */ 0.0001f);
@@ -730,12 +650,11 @@
         final float y0 = GAMMA_CORRECTION_SPLINE.interpolate(x0);
         final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
         final float y4 = GAMMA_CORRECTION_SPLINE.interpolate(x4);
-        Resources resources = createResources(EMPTY_INT_ARRAY);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX,
-                GAMMA_CORRECTION_NITS);
+        Resources resources = createResources();
+        DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(GAMMA_CORRECTION_LUX)
+                .setAutoBrightnessLevelsNits(GAMMA_CORRECTION_NITS).build();
         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
-                mMockDwbc);
+                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
         // Validity, as per tradition:
         assertEquals(y0, strategy.getBrightness(x0), 0.0001f /* tolerance */);
         assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
@@ -757,4 +676,57 @@
         assertEquals(1.0f, strategy.getBrightness(x4), 0.0001f /* tolerance */);
         assertEquals(adjustment, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
     }
+
+    @Test
+    public void testGetMode() {
+        Resources res = createResources(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE);
+        DisplayDeviceConfig ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE)
+                .build();
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
+                AUTO_BRIGHTNESS_MODE_IDLE,
+                mMockDwbc);
+        assertEquals(AUTO_BRIGHTNESS_MODE_IDLE, strategy.getMode());
+    }
+
+    private static class DdcBuilder {
+        private DisplayDeviceConfig mDdc;
+
+        DdcBuilder() {
+            mDdc = mock(DisplayDeviceConfig.class);
+            when(mDdc.getNits()).thenReturn(DISPLAY_RANGE_NITS);
+            when(mDdc.getBrightness()).thenReturn(DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT);
+            when(mDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(LUX_LEVELS);
+            when(mDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(EMPTY_FLOAT_ARRAY);
+            when(mDdc.getAutoBrightnessBrighteningLevels()).thenReturn(EMPTY_FLOAT_ARRAY);
+        }
+
+        DdcBuilder setNitsRange(float[] nitsArray) {
+            when(mDdc.getNits()).thenReturn(nitsArray);
+            return this;
+        }
+
+        DdcBuilder setBrightnessRange(float[] brightnessArray) {
+            when(mDdc.getBrightness()).thenReturn(brightnessArray);
+            return this;
+        }
+
+        DdcBuilder setAutoBrightnessLevelsLux(float[] luxLevels) {
+            when(mDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(luxLevels);
+            return this;
+        }
+
+        DdcBuilder setAutoBrightnessLevelsNits(float[] brightnessLevelsNits) {
+            when(mDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(brightnessLevelsNits);
+            return this;
+        }
+
+        DdcBuilder setAutoBrightnessLevels(float[] brightnessLevels) {
+            when(mDdc.getAutoBrightnessBrighteningLevels()).thenReturn(brightnessLevels);
+            return this;
+        }
+
+        DisplayDeviceConfig build() {
+            return mDdc;
+        }
+    }
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 31d7e88..a4c15b5 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -17,6 +17,7 @@
 package com.android.server.display;
 
 
+import static com.android.internal.display.BrightnessSynchronizer.brightnessIntToFloat;
 import static com.android.server.display.config.SensorData.SupportedMode;
 import static com.android.server.display.utils.DeviceConfigParsingUtils.ambientBrightnessThresholdsIntToFloat;
 import static com.android.server.display.utils.DeviceConfigParsingUtils.displayBrightnessThresholdsIntToFloat;
@@ -47,7 +48,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
-import com.android.internal.display.BrightnessSynchronizer;
 import com.android.server.display.config.HdrBrightnessData;
 import com.android.server.display.config.ThermalStatus;
 import com.android.server.display.feature.DisplayManagerFlags;
@@ -609,6 +609,9 @@
                 float[]{2.0f, 200.0f, 600.0f}, ZERO_DELTA);
         assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
                 float[]{0.0f, 110.0f, 500.0f}, ZERO_DELTA);
+        assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(), new
+                float[]{brightnessIntToFloat(50), brightnessIntToFloat(100),
+                brightnessIntToFloat(150)}, SMALL_DELTA);
 
         // Test thresholds
         assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), ZERO_DELTA);
@@ -674,7 +677,7 @@
         assertEquals("test_light_sensor", mDisplayDeviceConfig.getAmbientLightSensor().type);
         assertEquals("", mDisplayDeviceConfig.getAmbientLightSensor().name);
 
-        assertEquals(BrightnessSynchronizer.brightnessIntToFloat(35),
+        assertEquals(brightnessIntToFloat(35),
                 mDisplayDeviceConfig.getBrightnessCapForWearBedtimeMode(), ZERO_DELTA);
     }
 
@@ -737,6 +740,27 @@
                 mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), ZERO_DELTA);
         assertArrayEquals(new float[]{0.2f, 0.3f},
                 mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(), SMALL_DELTA);
+
+        assertArrayEquals(new float[]{0.0f, 90},
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux("default", "dim"),
+                ZERO_DELTA);
+        assertArrayEquals(new float[]{0.3f, 0.4f},
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels("default", "dim"),
+                SMALL_DELTA);
+
+        assertArrayEquals(new float[]{0.0f, 95},
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux("doze", "normal"),
+                ZERO_DELTA);
+        assertArrayEquals(new float[]{0.35f, 0.45f},
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels("doze", "normal"),
+                SMALL_DELTA);
+
+        assertArrayEquals(new float[]{0.0f, 100},
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux("doze", "bright"),
+                ZERO_DELTA);
+        assertArrayEquals(new float[]{0.4f, 0.5f},
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels("doze", "bright"),
+                SMALL_DELTA);
     }
 
     @Test
@@ -746,7 +770,9 @@
         setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(),
                 getValidProxSensor(), /* includeIdleMode= */ false));
 
-        assertNull(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels());
+        assertArrayEquals(new float[]{brightnessIntToFloat(50), brightnessIntToFloat(100),
+                        brightnessIntToFloat(150)},
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(), SMALL_DELTA);
         assertArrayEquals(new float[]{0, 110, 500},
                 mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), ZERO_DELTA);
         assertArrayEquals(new float[]{2, 200, 600},
@@ -1138,6 +1164,46 @@
                 +               "</point>\n"
                 +           "</map>\n"
                 +       "</luxToBrightnessMapping>\n"
+                +       "<luxToBrightnessMapping>\n"
+                +           "<setting>dim</setting>\n"
+                +           "<map>\n"
+                +               "<point>\n"
+                +                   "<first>0</first>\n"
+                +                   "<second>0.3</second>\n"
+                +               "</point>\n"
+                +               "<point>\n"
+                +                   "<first>90</first>\n"
+                +                   "<second>0.4</second>\n"
+                +               "</point>\n"
+                +           "</map>\n"
+                +       "</luxToBrightnessMapping>\n"
+                +       "<luxToBrightnessMapping>\n"
+                +           "<mode>doze</mode>\n"
+                +           "<map>\n"
+                +               "<point>\n"
+                +                   "<first>0</first>\n"
+                +                   "<second>0.35</second>\n"
+                +               "</point>\n"
+                +               "<point>\n"
+                +                   "<first>95</first>\n"
+                +                   "<second>0.45</second>\n"
+                +               "</point>\n"
+                +           "</map>\n"
+                +       "</luxToBrightnessMapping>\n"
+                +       "<luxToBrightnessMapping>\n"
+                +           "<mode>doze</mode>\n"
+                +           "<setting>bright</setting>\n"
+                +           "<map>\n"
+                +               "<point>\n"
+                +                   "<first>0</first>\n"
+                +                   "<second>0.4</second>\n"
+                +               "</point>\n"
+                +               "<point>\n"
+                +                   "<first>100</first>\n"
+                +                   "<second>0.5</second>\n"
+                +               "</point>\n"
+                +           "</map>\n"
+                +       "</luxToBrightnessMapping>\n"
                 +   "</autoBrightness>\n"
                 +  getPowerThrottlingConfig()
                 +   "<highBrightnessMode enabled=\"true\">\n"
@@ -1435,6 +1501,10 @@
         when(mResources.getIntArray(
                 com.android.internal.R.array.config_autoBrightnessLevels))
                 .thenReturn(screenBrightnessLevelLux);
+        int[] screenBrightnessLevels = new int[]{50, 100, 150};
+        when(mResources.getIntArray(
+                com.android.internal.R.array.config_autoBrightnessLcdBacklightValues))
+                .thenReturn(screenBrightnessLevels);
 
         // Thresholds
         // Config.xml requires the levels arrays to be of length N and the thresholds arrays to be
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
index dea838d..fbb14c3 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -88,4 +89,12 @@
 
         verify(mDisplayPowerController).setBrightnessFromOffload(brightness);
     }
+
+    @Test
+    public void testBlockScreenOn() {
+        Runnable unblocker = () -> {};
+        mSession.blockScreenOn(unblocker);
+
+        verify(mDisplayOffloader).onBlockingScreenOn(eq(unblocker));
+    }
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
index acd9dce..02bd35a 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -18,6 +18,7 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
 
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.any;
@@ -28,7 +29,6 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isA;
-import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
@@ -62,6 +62,7 @@
 import android.provider.Settings;
 import android.testing.TestableContext;
 import android.util.FloatProperty;
+import android.util.SparseArray;
 import android.view.Display;
 import android.view.DisplayInfo;
 
@@ -357,7 +358,7 @@
         float followerBrightness = 0.4f;
         float nits = 300;
         when(mHolder.automaticBrightnessController.convertToNits(leadBrightness)).thenReturn(nits);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+        when(followerDpc.automaticBrightnessController.getBrightnessFromNits(nits))
                 .thenReturn(followerBrightness);
         when(mHolder.brightnessSetting.getBrightness()).thenReturn(leadBrightness);
         listener.onBrightnessChanged(leadBrightness);
@@ -372,7 +373,7 @@
         float brightness = 0.6f;
         nits = 600;
         when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+        when(followerDpc.automaticBrightnessController.getBrightnessFromNits(nits))
                 .thenReturn(brightness);
         when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
         listener.onBrightnessChanged(brightness);
@@ -404,7 +405,7 @@
 
         float brightness = 0.3f;
         when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(300f);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(anyFloat()))
+        when(followerDpc.automaticBrightnessController.getBrightnessFromNits(anyFloat()))
                 .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
         when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
         listener.onBrightnessChanged(brightness);
@@ -466,7 +467,7 @@
 
         float brightness = 0.3f;
         when(mHolder.automaticBrightnessController.convertToNits(anyFloat())).thenReturn(-1f);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(anyFloat()))
+        when(followerDpc.automaticBrightnessController.getBrightnessFromNits(anyFloat()))
                 .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
         when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
         listener.onBrightnessChanged(brightness);
@@ -500,7 +501,7 @@
         when(mHolder.automaticBrightnessController.convertToNits(rawLeadBrightness))
                 .thenReturn(nits);
         when(mHolder.automaticBrightnessController.getAmbientLux()).thenReturn(ambientLux);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+        when(followerDpc.automaticBrightnessController.getBrightnessFromNits(nits))
                 .thenReturn(followerBrightness);
 
         mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
@@ -534,7 +535,7 @@
         when(mHolder.automaticBrightnessController.convertToNits(rawLeadBrightness))
                 .thenReturn(nits);
         when(mHolder.automaticBrightnessController.getAmbientLux()).thenReturn(ambientLux);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+        when(followerDpc.automaticBrightnessController.getBrightnessFromNits(nits))
                 .thenReturn(followerBrightness);
 
         mHolder.dpc.updateBrightness();
@@ -592,9 +593,9 @@
         float brightness = 0.6f;
         float nits = 600;
         when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+        when(followerDpc.automaticBrightnessController.getBrightnessFromNits(nits))
                 .thenReturn(brightness);
-        when(secondFollowerDpc.automaticBrightnessController.convertToFloatScale(nits))
+        when(secondFollowerDpc.automaticBrightnessController.getBrightnessFromNits(nits))
                 .thenReturn(brightness);
         when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
         listener.onBrightnessChanged(brightness);
@@ -625,9 +626,9 @@
         brightness = 0.7f;
         nits = 700;
         when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+        when(followerDpc.automaticBrightnessController.getBrightnessFromNits(nits))
                 .thenReturn(brightness);
-        when(secondFollowerDpc.automaticBrightnessController.convertToFloatScale(nits))
+        when(secondFollowerDpc.automaticBrightnessController.getBrightnessFromNits(nits))
                 .thenReturn(brightness);
         when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
         listener.onBrightnessChanged(brightness);
@@ -697,9 +698,9 @@
         float brightness = 0.6f;
         float nits = 600;
         when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(followerHolder.automaticBrightnessController.convertToFloatScale(nits))
+        when(followerHolder.automaticBrightnessController.getBrightnessFromNits(nits))
                 .thenReturn(brightness);
-        when(secondFollowerHolder.automaticBrightnessController.convertToFloatScale(nits))
+        when(secondFollowerHolder.automaticBrightnessController.getBrightnessFromNits(nits))
                 .thenReturn(brightness);
         when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
         listener.onBrightnessChanged(brightness);
@@ -1124,7 +1125,7 @@
 
         float newBrightness = 0.4f;
         when(mHolder.brightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
-        when(mHolder.automaticBrightnessController.convertToFloatScale(nits))
+        when(mHolder.automaticBrightnessController.getBrightnessFromNits(nits))
                 .thenReturn(newBrightness);
         // New display device
         setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
@@ -1141,12 +1142,9 @@
     @Test
     public void testShortTermModelPersistsWhenDisplayDeviceChanges() {
         float lux = 2000;
-        float brightness = 0.4f;
         float nits = 500;
-        when(mHolder.brightnessMappingStrategy.getUserLux()).thenReturn(lux);
-        when(mHolder.brightnessMappingStrategy.getUserBrightness()).thenReturn(brightness);
-        when(mHolder.brightnessMappingStrategy.convertToNits(brightness)).thenReturn(nits);
-        when(mHolder.brightnessMappingStrategy.convertToFloatScale(nits)).thenReturn(brightness);
+        when(mHolder.automaticBrightnessController.getUserLux()).thenReturn(lux);
+        when(mHolder.automaticBrightnessController.getUserNits()).thenReturn(nits);
         DisplayPowerRequest dpr = new DisplayPowerRequest();
         mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
         advanceTime(1);
@@ -1163,7 +1161,7 @@
                 any(Looper.class),
                 eq(mSensorManagerMock),
                 /* lightSensor= */ any(),
-                eq(mHolder.brightnessMappingStrategy),
+                /* brightnessMappingStrategyMap= */ any(SparseArray.class),
                 /* lightSensorWarmUpTime= */ anyInt(),
                 /* brightnessMin= */ anyFloat(),
                 /* brightnessMax= */ anyFloat(),
@@ -1182,11 +1180,10 @@
                 eq(mContext),
                 any(BrightnessRangeController.class),
                 any(BrightnessThrottler.class),
-                /* idleModeBrightnessMapper= */ isNull(),
                 /* ambientLightHorizonShort= */ anyInt(),
                 /* ambientLightHorizonLong= */ anyInt(),
                 eq(lux),
-                eq(brightness)
+                eq(nits)
         );
     }
 
@@ -1219,7 +1216,7 @@
     public void testDwbcCallsHappenOnHandler() {
         mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
 
-        mHolder.dpc.setAutomaticScreenBrightnessMode(true);
+        mHolder.dpc.setAutomaticScreenBrightnessMode(AUTO_BRIGHTNESS_MODE_IDLE);
         verify(mDisplayWhiteBalanceControllerMock, never()).setStrongModeEnabled(true);
 
         // dispatch handler looper
@@ -1382,7 +1379,7 @@
                 BRIGHTNESS_RAMP_DECREASE_MAX);
 
         // switch to idle mode
-        mHolder.dpc.setAutomaticScreenBrightnessMode(/* idle= */ true);
+        mHolder.dpc.setAutomaticScreenBrightnessMode(AUTO_BRIGHTNESS_MODE_IDLE);
         advanceTime(1);
 
         // A second time, when switching to idle mode.
@@ -1411,7 +1408,7 @@
                 BRIGHTNESS_RAMP_DECREASE_MAX);
 
         // switch to idle mode
-        mHolder.dpc.setAutomaticScreenBrightnessMode(/* idle= */ true);
+        mHolder.dpc.setAutomaticScreenBrightnessMode(AUTO_BRIGHTNESS_MODE_IDLE);
         advanceTime(1);
 
         // A second time, when switching to idle mode.
@@ -1437,7 +1434,7 @@
                 mHolder.config, /* isEnabled= */ true);
 
         // switch to idle mode
-        mHolder.dpc.setAutomaticScreenBrightnessMode(true);
+        mHolder.dpc.setAutomaticScreenBrightnessMode(AUTO_BRIGHTNESS_MODE_IDLE);
 
         // A second time when switching to idle mode.
         verify(mHolder.animator, times(2)).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX,
@@ -1463,7 +1460,7 @@
                 mHolder.config, /* isEnabled= */ true);
 
         // switch to idle mode
-        mHolder.dpc.setAutomaticScreenBrightnessMode(true);
+        mHolder.dpc.setAutomaticScreenBrightnessMode(AUTO_BRIGHTNESS_MODE_IDLE);
 
         verify(mHolder.animator).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX_IDLE,
                 BRIGHTNESS_RAMP_DECREASE_MAX_IDLE);
@@ -1839,7 +1836,7 @@
         AutomaticBrightnessController getAutomaticBrightnessController(
                 AutomaticBrightnessController.Callbacks callbacks, Looper looper,
                 SensorManager sensorManager, Sensor lightSensor,
-                BrightnessMappingStrategy interactiveModeBrightnessMapper,
+                SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap,
                 int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
                 float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
                 long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
@@ -1850,15 +1847,13 @@
                 HysteresisLevels ambientBrightnessThresholdsIdle,
                 HysteresisLevels screenBrightnessThresholdsIdle, Context context,
                 BrightnessRangeController brightnessRangeController,
-                BrightnessThrottler brightnessThrottler,
-                BrightnessMappingStrategy idleModeBrightnessMapper,
-                int ambientLightHorizonShort, int ambientLightHorizonLong, float userLux,
-                float userBrightness) {
+                BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort,
+                int ambientLightHorizonLong, float userLux, float userNits) {
             return mAutomaticBrightnessController;
         }
 
         @Override
-        BrightnessMappingStrategy getInteractiveModeBrightnessMapper(Resources resources,
+        BrightnessMappingStrategy getDefaultModeBrightnessMapper(Resources resources,
                 DisplayDeviceConfig displayDeviceConfig,
                 DisplayWhiteBalanceController displayWhiteBalanceController) {
             return mBrightnessMappingStrategy;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 50b0e16..64cdac4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -18,6 +18,7 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
 
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.any;
@@ -28,7 +29,6 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isA;
-import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
@@ -62,6 +62,7 @@
 import android.provider.Settings;
 import android.testing.TestableContext;
 import android.util.FloatProperty;
+import android.util.SparseArray;
 import android.view.Display;
 import android.view.DisplayInfo;
 
@@ -357,7 +358,7 @@
         float followerBrightness = 0.4f;
         float nits = 300;
         when(mHolder.automaticBrightnessController.convertToNits(leadBrightness)).thenReturn(nits);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+        when(followerDpc.automaticBrightnessController.getBrightnessFromNits(nits))
                 .thenReturn(followerBrightness);
         when(mHolder.brightnessSetting.getBrightness()).thenReturn(leadBrightness);
         listener.onBrightnessChanged(leadBrightness);
@@ -375,7 +376,7 @@
         float brightness = 0.6f;
         nits = 600;
         when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+        when(followerDpc.automaticBrightnessController.getBrightnessFromNits(nits))
                 .thenReturn(brightness);
         when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
         listener.onBrightnessChanged(brightness);
@@ -408,7 +409,7 @@
 
         float brightness = 0.3f;
         when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(300f);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(anyFloat()))
+        when(followerDpc.automaticBrightnessController.getBrightnessFromNits(anyFloat()))
                 .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
         when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
         listener.onBrightnessChanged(brightness);
@@ -472,7 +473,7 @@
 
         float brightness = 0.3f;
         when(mHolder.automaticBrightnessController.convertToNits(anyFloat())).thenReturn(-1f);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(anyFloat()))
+        when(followerDpc.automaticBrightnessController.getBrightnessFromNits(anyFloat()))
                 .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
         when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
         listener.onBrightnessChanged(brightness);
@@ -507,7 +508,7 @@
         when(mHolder.automaticBrightnessController.convertToNits(rawLeadBrightness))
                 .thenReturn(nits);
         when(mHolder.automaticBrightnessController.getAmbientLux()).thenReturn(ambientLux);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+        when(followerDpc.automaticBrightnessController.getBrightnessFromNits(nits))
                 .thenReturn(followerBrightness);
 
         mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
@@ -541,7 +542,7 @@
         when(mHolder.automaticBrightnessController.convertToNits(rawLeadBrightness))
                 .thenReturn(nits);
         when(mHolder.automaticBrightnessController.getAmbientLux()).thenReturn(ambientLux);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+        when(followerDpc.automaticBrightnessController.getBrightnessFromNits(nits))
                 .thenReturn(followerBrightness);
 
         mHolder.dpc.updateBrightness();
@@ -600,9 +601,9 @@
         float brightness = 0.6f;
         float nits = 600;
         when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+        when(followerDpc.automaticBrightnessController.getBrightnessFromNits(nits))
                 .thenReturn(brightness);
-        when(secondFollowerDpc.automaticBrightnessController.convertToFloatScale(nits))
+        when(secondFollowerDpc.automaticBrightnessController.getBrightnessFromNits(nits))
                 .thenReturn(brightness);
         when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
         listener.onBrightnessChanged(brightness);
@@ -633,9 +634,9 @@
         brightness = 0.7f;
         nits = 700;
         when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+        when(followerDpc.automaticBrightnessController.getBrightnessFromNits(nits))
                 .thenReturn(brightness);
-        when(secondFollowerDpc.automaticBrightnessController.convertToFloatScale(nits))
+        when(secondFollowerDpc.automaticBrightnessController.getBrightnessFromNits(nits))
                 .thenReturn(brightness);
         when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
         listener.onBrightnessChanged(brightness);
@@ -706,9 +707,9 @@
         float brightness = 0.6f;
         float nits = 600;
         when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(followerHolder.automaticBrightnessController.convertToFloatScale(nits))
+        when(followerHolder.automaticBrightnessController.getBrightnessFromNits(nits))
                 .thenReturn(brightness);
-        when(secondFollowerHolder.automaticBrightnessController.convertToFloatScale(nits))
+        when(secondFollowerHolder.automaticBrightnessController.getBrightnessFromNits(nits))
                 .thenReturn(brightness);
         when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
         listener.onBrightnessChanged(brightness);
@@ -1056,7 +1057,7 @@
 
         float newBrightness = 0.4f;
         when(mHolder.brightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
-        when(mHolder.automaticBrightnessController.convertToFloatScale(nits))
+        when(mHolder.automaticBrightnessController.getBrightnessFromNits(nits))
                 .thenReturn(newBrightness);
         // New display device
         setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
@@ -1073,12 +1074,9 @@
     @Test
     public void testShortTermModelPersistsWhenDisplayDeviceChanges() {
         float lux = 2000;
-        float brightness = 0.4f;
         float nits = 500;
-        when(mHolder.brightnessMappingStrategy.getUserLux()).thenReturn(lux);
-        when(mHolder.brightnessMappingStrategy.getUserBrightness()).thenReturn(brightness);
-        when(mHolder.brightnessMappingStrategy.convertToNits(brightness)).thenReturn(nits);
-        when(mHolder.brightnessMappingStrategy.convertToFloatScale(nits)).thenReturn(brightness);
+        when(mHolder.automaticBrightnessController.getUserLux()).thenReturn(lux);
+        when(mHolder.automaticBrightnessController.getUserNits()).thenReturn(nits);
         DisplayPowerRequest dpr = new DisplayPowerRequest();
         mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
         advanceTime(1);
@@ -1095,7 +1093,7 @@
                 any(Looper.class),
                 eq(mSensorManagerMock),
                 /* lightSensor= */ any(),
-                eq(mHolder.brightnessMappingStrategy),
+                /* brightnessMappingStrategyMap= */ any(SparseArray.class),
                 /* lightSensorWarmUpTime= */ anyInt(),
                 /* brightnessMin= */ anyFloat(),
                 /* brightnessMax= */ anyFloat(),
@@ -1114,11 +1112,10 @@
                 eq(mContext),
                 any(BrightnessRangeController.class),
                 any(BrightnessThrottler.class),
-                /* idleModeBrightnessMapper= */ isNull(),
                 /* ambientLightHorizonShort= */ anyInt(),
                 /* ambientLightHorizonLong= */ anyInt(),
                 eq(lux),
-                eq(brightness)
+                eq(nits)
         );
     }
 
@@ -1151,7 +1148,7 @@
     public void testDwbcCallsHappenOnHandler() {
         mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
 
-        mHolder.dpc.setAutomaticScreenBrightnessMode(true);
+        mHolder.dpc.setAutomaticScreenBrightnessMode(AUTO_BRIGHTNESS_MODE_IDLE);
         verify(mDisplayWhiteBalanceControllerMock, never()).setStrongModeEnabled(true);
 
         // dispatch handler looper
@@ -1293,7 +1290,7 @@
                 BRIGHTNESS_RAMP_DECREASE_MAX);
 
         // switch to idle
-        mHolder.dpc.setAutomaticScreenBrightnessMode(/* idle= */ true);
+        mHolder.dpc.setAutomaticScreenBrightnessMode(AUTO_BRIGHTNESS_MODE_IDLE);
         advanceTime(1);
 
         verify(mHolder.animator, times(2)).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX,
@@ -1320,7 +1317,7 @@
                 BRIGHTNESS_RAMP_DECREASE_MAX);
 
         // switch to idle
-        mHolder.dpc.setAutomaticScreenBrightnessMode(/* idle= */ true);
+        mHolder.dpc.setAutomaticScreenBrightnessMode(AUTO_BRIGHTNESS_MODE_IDLE);
         advanceTime(1);
 
         verify(mHolder.animator).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX_IDLE,
@@ -1347,7 +1344,7 @@
                 mHolder.config, /* isEnabled= */ true);
 
         // switch to idle mode
-        mHolder.dpc.setAutomaticScreenBrightnessMode(true);
+        mHolder.dpc.setAutomaticScreenBrightnessMode(AUTO_BRIGHTNESS_MODE_IDLE);
 
         // second time when switching to idle screen brightness mode
         verify(mHolder.animator, times(2)).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX,
@@ -1372,7 +1369,7 @@
                 mHolder.config, /* isEnabled= */ true);
 
         // switch to idle mode
-        mHolder.dpc.setAutomaticScreenBrightnessMode(true);
+        mHolder.dpc.setAutomaticScreenBrightnessMode(AUTO_BRIGHTNESS_MODE_IDLE);
 
         verify(mHolder.animator).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX_IDLE,
                 BRIGHTNESS_RAMP_DECREASE_MAX_IDLE);
@@ -1655,7 +1652,7 @@
         AutomaticBrightnessController getAutomaticBrightnessController(
                 AutomaticBrightnessController.Callbacks callbacks, Looper looper,
                 SensorManager sensorManager, Sensor lightSensor,
-                BrightnessMappingStrategy interactiveModeBrightnessMapper,
+                SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap,
                 int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
                 float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
                 long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
@@ -1666,15 +1663,13 @@
                 HysteresisLevels ambientBrightnessThresholdsIdle,
                 HysteresisLevels screenBrightnessThresholdsIdle, Context context,
                 BrightnessRangeController brightnessRangeController,
-                BrightnessThrottler brightnessThrottler,
-                BrightnessMappingStrategy idleModeBrightnessMapper,
-                int ambientLightHorizonShort, int ambientLightHorizonLong, float userLux,
-                float userBrightness) {
+                BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort,
+                int ambientLightHorizonLong, float userLux, float userNits) {
             return mAutomaticBrightnessController;
         }
 
         @Override
-        BrightnessMappingStrategy getInteractiveModeBrightnessMapper(Resources resources,
+        BrightnessMappingStrategy getDefaultModeBrightnessMapper(Resources resources,
                 DisplayDeviceConfig displayDeviceConfig,
                 DisplayWhiteBalanceController displayWhiteBalanceController) {
             return mBrightnessMappingStrategy;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index f36854b..00f9892 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -206,6 +206,9 @@
         when(mMockedResources.getIntArray(
             com.android.internal.R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
             .thenReturn(new int[]{});
+        when(mMockedResources.getIntArray(
+                com.android.internal.R.array.config_autoBrightnessLcdBacklightValues))
+                .thenReturn(new int[]{});
         doReturn(true).when(mFlags).isDisplayOffloadEnabled();
         initDisplayOffloadSession();
     }
@@ -1235,6 +1238,9 @@
 
             @Override
             public void stopOffload() {}
+
+            @Override
+            public void onBlockingScreenOn(Runnable unblocker) {}
         });
 
         mDisplayOffloadSession = new DisplayOffloadSessionImpl(mDisplayOffloader,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
index 52fa91f..2d0c3fd 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -293,21 +293,21 @@
     }
 
     @Test
-    public void testConvertToFloatScale() {
+    public void testGetBrightnessFromNits() {
         float brightness = 0.5f;
         float nits = 300;
 
         // ABC is null
         assertEquals(PowerManager.BRIGHTNESS_INVALID_FLOAT,
-                mDisplayBrightnessController.convertToFloatScale(nits), /* delta= */ 0);
+                mDisplayBrightnessController.getBrightnessFromNits(nits), /* delta= */ 0);
 
         AutomaticBrightnessController automaticBrightnessController =
                 mock(AutomaticBrightnessController.class);
-        when(automaticBrightnessController.convertToFloatScale(nits)).thenReturn(brightness);
+        when(automaticBrightnessController.getBrightnessFromNits(nits)).thenReturn(brightness);
         mDisplayBrightnessController.setAutomaticBrightnessController(
                 automaticBrightnessController);
 
-        assertEquals(brightness, mDisplayBrightnessController.convertToFloatScale(nits),
+        assertEquals(brightness, mDisplayBrightnessController.getBrightnessFromNits(nits),
                 /* delta= */ 0);
     }
 
@@ -329,7 +329,7 @@
         float brightness = 0.3f;
         AutomaticBrightnessController automaticBrightnessController =
                 mock(AutomaticBrightnessController.class);
-        when(automaticBrightnessController.convertToFloatScale(nits)).thenReturn(brightness);
+        when(automaticBrightnessController.getBrightnessFromNits(nits)).thenReturn(brightness);
         when(mBrightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
         mDisplayBrightnessController.setAutomaticBrightnessController(
                 automaticBrightnessController);
@@ -340,7 +340,7 @@
         // When the nits value is invalid, the brightness is resumed from where it was last set
         nits = -1;
         brightness = 0.4f;
-        when(automaticBrightnessController.convertToFloatScale(nits)).thenReturn(brightness);
+        when(automaticBrightnessController.getBrightnessFromNits(nits)).thenReturn(brightness);
         when(mBrightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
         when(mBrightnessSetting.getBrightness()).thenReturn(brightness);
         mDisplayBrightnessController.setAutomaticBrightnessController(
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
index b3605cc..caa0864 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
@@ -221,6 +221,7 @@
         r.setStartUid(myUid());
         r.setHostingRecord(new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST));
         r.makeActive(thread, mAms.mProcessStats);
+        ProcessRecord.updateProcessRecordNodes(r);
         doNothing().when(r).killLocked(any(), any(), anyInt(), anyInt(), anyBoolean(),
                 anyBoolean());
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index e4da2b6..cc6fc80 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -1756,6 +1756,7 @@
     private ProcessRecord makeProcessRecord(ApplicationInfo info) {
         final ProcessRecord r = spy(new ProcessRecord(mAms, info, info.processName, info.uid));
         r.setPid(mNextPid.incrementAndGet());
+        ProcessRecord.updateProcessRecordNodes(r);
         return r;
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 0fd424b..115a5b0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -336,6 +336,7 @@
         final ProcessRecord r = spy(new ProcessRecord(mAms, ai, processName, ai.uid));
         r.mState = spy(r.mState);
         r.setPid(mNextPid.getAndIncrement());
+        ProcessRecord.updateProcessRecordNodes(r);
         mActiveProcesses.add(r);
 
         final IApplicationThread thread;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index f45dd39..f386c3b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -114,7 +114,9 @@
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -258,25 +260,40 @@
         ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
         lru.clear();
         Collections.addAll(lru, apps);
-        for (ProcessRecord app : apps) {
-            sService.mOomAdjuster.onProcessBeginLocked(app);
-        }
     }
 
     /**
      * Run updateOomAdjLocked().
+     * - If there is no process specified, run updateOomAdjLocked(int) on existing lru
      * - If there's only one process, then it calls updateOomAdjLocked(ProcessRecord, int).
      * - Otherwise, sets the processes to the LRU and run updateOomAdjLocked(int).
      */
     @SuppressWarnings("GuardedBy")
     private void updateOomAdj(ProcessRecord... apps) {
-        if (apps.length == 1) {
-            sService.mOomAdjuster.onProcessBeginLocked(apps[0]);
-            sService.mOomAdjuster.updateOomAdjLocked(apps[0], OOM_ADJ_REASON_NONE);
-        } else {
-            setProcessesToLru(apps);
+        if (apps.length == 0) {
+            updateProcessRecordNodes(sService.mProcessList.getLruProcessesLOSP());
             sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
-            sService.mProcessList.getLruProcessesLOSP().clear();
+        } else {
+            updateProcessRecordNodes(Arrays.asList(apps));
+            if (apps.length == 1) {
+                sService.mOomAdjuster.updateOomAdjLocked(apps[0], OOM_ADJ_REASON_NONE);
+            } else {
+                setProcessesToLru(apps);
+                sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
+                sService.mProcessList.getLruProcessesLOSP().clear();
+            }
+        }
+    }
+
+    /**
+     * Fix up the pointers in the {@link ProcessRecordNode#mApp}:
+     * because we used the mokito spy objects all over the tests here, but the internal
+     * pointers in the {@link ProcessRecordNode#mApp} actually point to the real object.
+     * This needs to be fixed up here.
+     */
+    private void updateProcessRecordNodes(List<ProcessRecord> apps) {
+        for (ProcessRecord app : apps) {
+            ProcessRecord.updateProcessRecordNodes(app);
         }
     }
 
@@ -613,7 +630,6 @@
             s.lastTopAlmostPerceptibleBindRequestUptimeMs = nowUptime;
             s.getConnections().clear();
             app.mServices.updateHasTopStartedAlmostPerceptibleServices();
-            sService.mOomAdjuster.onProcessBeginLocked(system);
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
             updateOomAdj(app);
 
@@ -636,7 +652,6 @@
             s.lastTopAlmostPerceptibleBindRequestUptimeMs =
                     nowUptime - 2 * sService.mConstants.mServiceBindAlmostPerceptibleTimeoutMs;
             app.mServices.updateHasTopStartedAlmostPerceptibleServices();
-            sService.mOomAdjuster.onProcessBeginLocked(system);
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
             updateOomAdj(app);
 
@@ -660,7 +675,6 @@
                     nowUptime - 2 * sService.mConstants.mServiceBindAlmostPerceptibleTimeoutMs;
             s.getConnections().clear();
             app.mServices.updateHasTopStartedAlmostPerceptibleServices();
-            sService.mOomAdjuster.onProcessBeginLocked(system);
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
             updateOomAdj(app);
 
@@ -679,7 +693,6 @@
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, true));
         system.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
         system.mState.setHasTopUi(true);
-        sService.mOomAdjuster.onProcessBeginLocked(system);
         // Simulate the system starting and binding to a service in the app.
         ServiceRecord s = bindService(app, system,
                 null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class));
@@ -1532,7 +1545,7 @@
 
         client2.mServices.setHasForegroundServices(false, 0, /* hasNoneType=*/false);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(client2, OOM_ADJ_REASON_NONE);
+        updateOomAdj(client2);
 
         assertEquals(PROCESS_STATE_CACHED_EMPTY, client2.mState.getSetProcState());
         assertEquals(PROCESS_STATE_CACHED_EMPTY, client.mState.getSetProcState());
@@ -2018,7 +2031,6 @@
         app.setPendingFinishAttach(true);
         app.mState.setHasForegroundActivities(false);
 
-        sService.mOomAdjuster.onProcessBeginLocked(app);
         sService.mOomAdjuster.setAttachingProcessStatesLSP(app);
         updateOomAdj(app);
 
@@ -2035,7 +2047,6 @@
         app.mState.setHasForegroundActivities(true);
         doReturn(app).when(sService).getTopApp();
 
-        sService.mOomAdjuster.onProcessBeginLocked(app);
         sService.mOomAdjuster.setAttachingProcessStatesLSP(app);
         updateOomAdj(app);
 
@@ -2106,7 +2117,7 @@
             setServiceMap(s3, MOCKAPP5_UID, cn3);
             setServiceMap(c2s, MOCKAPP3_UID, cn4);
             app2UidRecord.setIdle(false);
-            sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
+            updateOomAdj();
 
             assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
                     SCHED_GROUP_DEFAULT);
@@ -2470,8 +2481,6 @@
         lru.clear();
         lru.add(app2);
         lru.add(app);
-        sService.mOomAdjuster.onProcessBeginLocked(app2);
-        sService.mOomAdjuster.onProcessBeginLocked(app);
 
         final ComponentName cn = ComponentName.unflattenFromString(
                 MOCKAPP_PACKAGENAME + "/.TestService");
@@ -2506,7 +2515,7 @@
         app2.mState.setHasShownUi(false);
 
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
+        updateOomAdj();
 
         assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-ui-services");
         assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj2, "cch-started-services");
@@ -2515,7 +2524,7 @@
         app.mState.setAdjType(null);
         app.mState.setSetAdj(UNKNOWN_ADJ);
         app.mState.setHasShownUi(false);
-        sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
+        updateOomAdj();
 
         assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
 
@@ -2524,7 +2533,7 @@
         app.mState.setAdjType(null);
         app.mState.setSetAdj(UNKNOWN_ADJ);
         s.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1;
-        sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
+        updateOomAdj();
 
         assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
 
@@ -2542,7 +2551,7 @@
         s.lastActivity = now;
 
         app.mServices.startService(s);
-        sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
+        updateOomAdj();
 
         assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
         assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
@@ -2553,7 +2562,7 @@
         app.mState.setSetAdj(UNKNOWN_ADJ);
         app.mState.setHasShownUi(false);
         s.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1;
-        sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
+        updateOomAdj();
 
         assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
         assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
@@ -2561,7 +2570,7 @@
         doReturn(userOther).when(sService.mUserController).getCurrentUserId();
         sService.mOomAdjuster.handleUserSwitchedLocked();
 
-        sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
+        updateOomAdj();
         assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
         assertProcStates(app2, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
     }
@@ -2584,7 +2593,7 @@
         app.mServices.updateHasAboveClientLocked();
         assertFalse(app.mServices.hasAboveClient());
 
-        sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
         assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
index ae53e70..7444403 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
@@ -19,6 +19,7 @@
 import android.app.AppOpsManager
 import android.content.Intent
 import android.content.pm.SuspendDialogInfo
+import android.content.pm.UserPackage
 import android.os.Binder
 import android.os.PersistableBundle
 import com.android.server.testutils.any
@@ -41,12 +42,18 @@
                 .thenReturn(AppOpsManager.MODE_DEFAULT)
     }
 
+    companion object {
+        val doUserPackage = UserPackage.of(TEST_USER_ID, DEVICE_OWNER_PACKAGE)
+        val platformUserPackage = UserPackage.of(TEST_USER_ID, PLATFORM_PACKAGE_NAME)
+        val testUserPackage1 = UserPackage.of(TEST_USER_ID, TEST_PACKAGE_1)
+    }
+
     @Test
     fun setPackagesSuspended() {
         val targetPackages = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
         val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 targetPackages, true /* suspended */, null /* appExtras */,
-                null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE,
+                null /* launcherExtras */, null /* dialogInfo */, doUserPackage,
                 TEST_USER_ID, deviceOwnerUid, false /* quarantined */)
         testHandler.flush()
 
@@ -63,14 +70,14 @@
     fun setPackagesSuspended_emptyPackageName() {
         var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 null /* packageNames */, true /* suspended */, null /* appExtras */,
-                null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE,
+                null /* launcherExtras */, null /* dialogInfo */, doUserPackage,
                 TEST_USER_ID, deviceOwnerUid, false /* quarantined */)
 
         assertThat(failedNames).isNull()
 
         failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 arrayOfNulls(0), true /* suspended */, null /* appExtras */,
-                null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE,
+                null /* launcherExtras */, null /* dialogInfo */, doUserPackage,
                 TEST_USER_ID, deviceOwnerUid, false /* quarantined */)
 
         assertThat(failedNames).isEmpty()
@@ -80,7 +87,8 @@
     fun setPackagesSuspended_callerIsNotAllowed() {
         val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 arrayOf(TEST_PACKAGE_2), true /* suspended */, null /* appExtras */,
-                null /* launcherExtras */, null /* dialogInfo */, TEST_PACKAGE_1, TEST_USER_ID,
+                null /* launcherExtras */, null /* dialogInfo */,
+                testUserPackage1, TEST_USER_ID,
                 Binder.getCallingUid(), false /* quarantined */)
 
         assertThat(failedNames).asList().hasSize(1)
@@ -91,7 +99,7 @@
     fun setPackagesSuspended_callerSuspendItself() {
         val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 arrayOf(DEVICE_OWNER_PACKAGE), true /* suspended */, null /* appExtras */,
-                null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE,
+                null /* launcherExtras */, null /* dialogInfo */, doUserPackage,
                 TEST_USER_ID, deviceOwnerUid, false /* quarantined */)
 
         assertThat(failedNames).asList().hasSize(1)
@@ -102,7 +110,7 @@
     fun setPackagesSuspended_nonexistentPackage() {
         val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 arrayOf(NONEXISTENT_PACKAGE), true /* suspended */, null /* appExtras */,
-                null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE,
+                null /* launcherExtras */, null /* dialogInfo */, doUserPackage,
                 TEST_USER_ID, deviceOwnerUid, false /* quarantined */)
 
         assertThat(failedNames).asList().hasSize(1)
@@ -115,7 +123,7 @@
             INSTALLER_PACKAGE, UNINSTALLER_PACKAGE, VERIFIER_PACKAGE, PERMISSION_CONTROLLER_PACKAGE)
         val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 knownPackages, true /* suspended */, null /* appExtras */,
-                null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE,
+                null /* launcherExtras */, null /* dialogInfo */, doUserPackage,
                 TEST_USER_ID, deviceOwnerUid, false /* quarantined */)!!
 
         assertThat(failedNames.size).isEqualTo(knownPackages.size)
@@ -129,14 +137,14 @@
         val targetPackages = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
         var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 targetPackages, true /* suspended */, null /* appExtras */,
-                null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE,
+                null /* launcherExtras */, null /* dialogInfo */, doUserPackage,
                 TEST_USER_ID, deviceOwnerUid, false /* quarantined */)
         testHandler.flush()
         Mockito.clearInvocations(broadcastHelper)
         assertThat(failedNames).isEmpty()
         failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 targetPackages, false /* suspended */, null /* appExtras */,
-                null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE,
+                null /* launcherExtras */, null /* dialogInfo */, doUserPackage,
                 TEST_USER_ID, deviceOwnerUid, false /* quarantined */)
         testHandler.flush()
 
@@ -184,7 +192,7 @@
         appExtras.putString(TEST_PACKAGE_1, TEST_PACKAGE_1)
         var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 arrayOf(TEST_PACKAGE_1), true /* suspended */, appExtras, null /* launcherExtras */,
-                null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid,
+                null /* dialogInfo */, doUserPackage, TEST_USER_ID, deviceOwnerUid,
                 false /* quarantined */)
         testHandler.flush()
         assertThat(failedNames).isEmpty()
@@ -202,22 +210,22 @@
         val targetPackages = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
         var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 targetPackages, true /* suspended */, appExtras, null /* launcherExtras */,
-                null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid,
+                null /* dialogInfo */, doUserPackage, TEST_USER_ID, deviceOwnerUid,
                 false /* quarantined */)
         testHandler.flush()
         Mockito.clearInvocations(broadcastHelper)
         assertThat(failedNames).isEmpty()
         assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
-            TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE)
+            TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isEqualTo(doUserPackage)
         assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
-            TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE)
+            TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(doUserPackage)
         assertThat(SuspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(),
             TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isNotNull()
         assertThat(SuspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(),
             TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isNotNull()
 
         suspendPackageHelper.removeSuspensionsBySuspendingPackage(pms.snapshotComputer(),
-            targetPackages, { suspendingPackage -> suspendingPackage == DEVICE_OWNER_PACKAGE },
+            targetPackages, { suspender -> suspender.packageName == DEVICE_OWNER_PACKAGE },
             TEST_USER_ID)
 
         testHandler.flush()
@@ -243,7 +251,7 @@
         launcherExtras.putString(TEST_PACKAGE_2, TEST_PACKAGE_2)
         var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 arrayOf(TEST_PACKAGE_2), true /* suspended */, null /* appExtras */, launcherExtras,
-                null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid,
+                null /* dialogInfo */, doUserPackage, TEST_USER_ID, deviceOwnerUid,
                 false /* quarantined */)
         testHandler.flush()
         assertThat(failedNames).isEmpty()
@@ -258,7 +266,7 @@
     fun isPackageSuspended() {
         var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 arrayOf(TEST_PACKAGE_1), true /* suspended */, null /* appExtras */,
-                null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE,
+                null /* launcherExtras */, null /* dialogInfo */, doUserPackage,
                 TEST_USER_ID, deviceOwnerUid, false /* quarantined */)
         testHandler.flush()
         assertThat(failedNames).isEmpty()
@@ -273,13 +281,13 @@
         launcherExtras.putString(TEST_PACKAGE_2, TEST_PACKAGE_2)
         var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 arrayOf(TEST_PACKAGE_2), true /* suspended */, null /* appExtras */, launcherExtras,
-                null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid,
+                null /* dialogInfo */, doUserPackage, TEST_USER_ID, deviceOwnerUid,
                 false /* quarantined */)
         testHandler.flush()
         assertThat(failedNames).isEmpty()
 
         assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
-            TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE)
+            TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(doUserPackage)
     }
 
     @Test
@@ -290,57 +298,57 @@
         // Suspend.
         var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 targetPackages, true /* suspended */, null /* appExtras */, launcherExtras,
-                null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid,
+                null /* dialogInfo */, doUserPackage, TEST_USER_ID, deviceOwnerUid,
                 false /* quarantined */)
         assertThat(failedNames).isEmpty()
         testHandler.flush()
 
         assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
-                TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE)
+                TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(doUserPackage)
 
         // Suspend by system.
         failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 targetPackages, true /* suspended */, null /* appExtras */, launcherExtras,
-                null /* dialogInfo */, PLATFORM_PACKAGE_NAME, TEST_USER_ID, deviceOwnerUid,
+                null /* dialogInfo */, platformUserPackage, TEST_USER_ID, deviceOwnerUid,
                 false /* quarantined */)
         assertThat(failedNames).isEmpty()
         testHandler.flush()
 
         assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
-                TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(PLATFORM_PACKAGE_NAME)
+                TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(platformUserPackage)
 
         // QAS by package1.
         failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 targetPackages, true /* suspended */, null /* appExtras */, launcherExtras,
-                null /* dialogInfo */, TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid,
+                null /* dialogInfo */, testUserPackage1, TEST_USER_ID, deviceOwnerUid,
                 true /* quarantined */)
         assertThat(failedNames).isEmpty()
         testHandler.flush()
 
         assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
-                TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(TEST_PACKAGE_1)
+                TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(testUserPackage1)
 
         // Un-QAS by package1.
         suspendPackageHelper.removeSuspensionsBySuspendingPackage(pms.snapshotComputer(),
-                targetPackages, { suspendingPackage -> suspendingPackage == TEST_PACKAGE_1 },
+                targetPackages, { suspendingPackage -> suspendingPackage == testUserPackage1 },
                 TEST_USER_ID)
         testHandler.flush()
 
         assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
-                TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(PLATFORM_PACKAGE_NAME)
+                TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(platformUserPackage)
 
         // Un-suspend by system.
         suspendPackageHelper.removeSuspensionsBySuspendingPackage(pms.snapshotComputer(),
-                targetPackages, { suspendingPackage -> suspendingPackage == PLATFORM_PACKAGE_NAME },
+                targetPackages, { suspender -> suspender.packageName == PLATFORM_PACKAGE_NAME },
                 TEST_USER_ID)
         testHandler.flush()
 
         assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
-                TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE)
+                TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(doUserPackage)
 
         // Unsuspend.
         suspendPackageHelper.removeSuspensionsBySuspendingPackage(pms.snapshotComputer(),
-                targetPackages, { suspendingPackage -> suspendingPackage == DEVICE_OWNER_PACKAGE },
+                targetPackages, { suspendingPackage -> suspendingPackage == doUserPackage },
                 TEST_USER_ID)
         testHandler.flush()
 
@@ -354,13 +362,13 @@
             .setTitle(TEST_PACKAGE_1).build()
         var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 arrayOf(TEST_PACKAGE_1), true /* suspended */, null /* appExtras */,
-                null /* launcherExtras */, dialogInfo, DEVICE_OWNER_PACKAGE, TEST_USER_ID,
+                null /* launcherExtras */, dialogInfo, doUserPackage, TEST_USER_ID,
                 deviceOwnerUid, false /* quarantined */)
         testHandler.flush()
         assertThat(failedNames).isEmpty()
 
         val result = suspendPackageHelper.getSuspendedDialogInfo(pms.snapshotComputer(),
-            TEST_PACKAGE_1, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)!!
+            TEST_PACKAGE_1, doUserPackage, TEST_USER_ID, deviceOwnerUid)!!
 
         assertThat(result.title).isEqualTo(TEST_PACKAGE_1)
     }
diff --git a/services/tests/servicestests/src/com/android/server/BootReceiverTest.java b/services/tests/servicestests/src/com/android/server/BootReceiverTest.java
deleted file mode 100644
index 523c5c0..0000000
--- a/services/tests/servicestests/src/com/android/server/BootReceiverTest.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.test.AndroidTestCase;
-
-import com.android.server.os.TombstoneProtos;
-import com.android.server.os.TombstoneProtos.Tombstone;
-
-public class BootReceiverTest extends AndroidTestCase {
-    private static final String TAG = "BootReceiverTest";
-
-    public void testRemoveMemoryFromTombstone() {
-        Tombstone tombstoneBase = Tombstone.newBuilder()
-                .setBuildFingerprint("build_fingerprint")
-                .setRevision("revision")
-                .setPid(123)
-                .setTid(23)
-                .setUid(34)
-                .setSelinuxLabel("selinux_label")
-                .addCommandLine("cmd1")
-                .addCommandLine("cmd2")
-                .addCommandLine("cmd3")
-                .setProcessUptime(300)
-                .setAbortMessage("abort")
-                .addCauses(TombstoneProtos.Cause.newBuilder()
-                        .setHumanReadable("cause1")
-                        .setMemoryError(TombstoneProtos.MemoryError.newBuilder()
-                                .setTool(TombstoneProtos.MemoryError.Tool.SCUDO)
-                                .setType(TombstoneProtos.MemoryError.Type.DOUBLE_FREE)))
-                .addLogBuffers(TombstoneProtos.LogBuffer.newBuilder().setName("name").addLogs(
-                        TombstoneProtos.LogMessage.newBuilder()
-                                .setTimestamp("123")
-                                .setMessage("message")))
-                .addOpenFds(TombstoneProtos.FD.newBuilder().setFd(1).setPath("path"))
-                .build();
-
-        Tombstone tombstoneWithoutMemory = tombstoneBase.toBuilder()
-                .putThreads(1, TombstoneProtos.Thread.newBuilder()
-                        .setId(1)
-                        .setName("thread1")
-                        .addRegisters(TombstoneProtos.Register.newBuilder().setName("r1").setU64(1))
-                        .addRegisters(TombstoneProtos.Register.newBuilder().setName("r2").setU64(2))
-                        .addBacktraceNote("backtracenote1")
-                        .addUnreadableElfFiles("files1")
-                        .setTaggedAddrCtrl(1)
-                        .setPacEnabledKeys(10)
-                        .build())
-                .build();
-
-        Tombstone tombstoneWithMemory = tombstoneBase.toBuilder()
-                .addMemoryMappings(TombstoneProtos.MemoryMapping.newBuilder()
-                        .setBeginAddress(1)
-                        .setEndAddress(100)
-                        .setOffset(10)
-                        .setRead(true)
-                        .setWrite(true)
-                        .setExecute(false)
-                        .setMappingName("mapping")
-                        .setBuildId("build")
-                        .setLoadBias(70))
-                .putThreads(1, TombstoneProtos.Thread.newBuilder()
-                        .setId(1)
-                        .setName("thread1")
-                        .addRegisters(TombstoneProtos.Register.newBuilder().setName("r1").setU64(1))
-                        .addRegisters(TombstoneProtos.Register.newBuilder().setName("r2").setU64(2))
-                        .addBacktraceNote("backtracenote1")
-                        .addUnreadableElfFiles("files1")
-                        .addMemoryDump(TombstoneProtos.MemoryDump.newBuilder()
-                                .setRegisterName("register1")
-                                .setMappingName("mapping")
-                                .setBeginAddress(10))
-                        .setTaggedAddrCtrl(1)
-                        .setPacEnabledKeys(10)
-                        .build())
-                .build();
-
-        assertThat(BootReceiver.removeMemoryFromTombstone(tombstoneWithMemory))
-                .isEqualTo(tombstoneWithoutMemory);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationPromptControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationPromptControllerTest.java
index 5fd28f57..332b1a2 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationPromptControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationPromptControllerTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.accessibility.magnification;
 
+import static android.content.pm.PackageManager.FEATURE_WINDOW_MAGNIFICATION;
 import static android.provider.Settings.Secure.ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT;
 
 import static com.android.internal.messages.nano.SystemMessageProto.SystemMessage.NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE;
@@ -44,6 +45,7 @@
 import androidx.test.InstrumentationRegistry;
 
 import org.junit.After;
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -70,6 +72,13 @@
     private WindowMagnificationPromptController mWindowMagnificationPromptController;
     private BroadcastReceiver mReceiver;
 
+    /**
+     *  return whether window magnification is supported for current test context.
+     */
+    private boolean isWindowModeSupported() {
+        return mTestableContext.getPackageManager().hasSystemFeature(FEATURE_WINDOW_MAGNIFICATION);
+    }
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -78,6 +87,9 @@
         setWindowMagnificationPromptSettings(true);
         mWindowMagnificationPromptController = new WindowMagnificationPromptController(
                 mTestableContext, TEST_USER);
+
+        // skip test if window magnification is not supported to prevent fail results.
+        Assume.assumeTrue(isWindowModeSupported());
     }
 
     @After
@@ -111,8 +123,8 @@
         final Intent intent = new Intent(ACTION_TURN_ON_IN_SETTINGS);
         mReceiver.onReceive(mTestableContext, intent);
 
-        assertThat(Settings.Secure.getInt(mResolver, ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT,
-                -1)).isEqualTo(0);
+        assertThat(Settings.Secure.getIntForUser(mResolver,
+                ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT, -1, TEST_USER)).isEqualTo(0);
         verify(mTestableContext.getSpyContext()).unregisterReceiver(mReceiver);
     }
 
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 3b5cae3..88b2ed4 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -50,14 +50,22 @@
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.IBiometricServiceReceiver;
 import android.hardware.biometrics.PromptInfo;
+import android.hardware.biometrics.fingerprint.SensorProps;
+import android.hardware.face.FaceSensorConfigurations;
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.face.IFaceService;
+import android.hardware.fingerprint.FingerprintSensorConfigurations;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintService;
 import android.hardware.iris.IIrisService;
 import android.os.Binder;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.test.InstrumentationRegistry;
@@ -89,6 +97,9 @@
 
     @Rule
     public MockitoRule mockitorule = MockitoJUnit.rule();
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
 
     @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
@@ -118,6 +129,10 @@
     @Captor
     private ArgumentCaptor<List<FingerprintSensorPropertiesInternal>> mFingerprintPropsCaptor;
     @Captor
+    private ArgumentCaptor<FingerprintSensorConfigurations> mFingerprintSensorConfigurationsCaptor;
+    @Captor
+    private ArgumentCaptor<FaceSensorConfigurations> mFaceSensorConfigurationsCaptor;
+    @Captor
     private ArgumentCaptor<List<FaceSensorPropertiesInternal>> mFacePropsCaptor;
 
     @Before
@@ -143,6 +158,9 @@
         when(mContext.getResources()).thenReturn(mResources);
         when(mInjector.getBiometricService()).thenReturn(mBiometricService);
         when(mInjector.getConfiguration(any())).thenReturn(config);
+        when(mInjector.getFaceConfiguration(any())).thenReturn(config);
+        when(mInjector.getFingerprintConfiguration(any())).thenReturn(config);
+        when(mInjector.getIrisConfiguration(any())).thenReturn(config);
         when(mInjector.getFingerprintService()).thenReturn(mFingerprintService);
         when(mInjector.getFaceService()).thenReturn(mFaceService);
         when(mInjector.getIrisService()).thenReturn(mIrisService);
@@ -173,12 +191,13 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(com.android.server.biometrics.Flags.FLAG_DE_HIDL)
     public void testRegisterAuthenticator_registerAuthenticators() throws Exception {
         final int fingerprintId = 0;
         final int fingerprintStrength = 15;
 
         final int faceId = 1;
-        final int faceStrength = 4095;
+        final int faceStrength = 15;
 
         final String[] config = {
                 // ID0:Fingerprint:Strong
@@ -206,6 +225,51 @@
                 Utils.authenticatorStrengthToPropertyStrength(faceStrength));
     }
 
+    @Test
+    @RequiresFlagsEnabled(com.android.server.biometrics.Flags.FLAG_DE_HIDL)
+    public void testRegisterAuthenticator_registerAuthenticatorsLegacy() throws RemoteException {
+        final int fingerprintId = 0;
+        final int fingerprintStrength = 15;
+
+        final int faceId = 1;
+        final int faceStrength = 4095;
+
+        final String[] config = {
+                // ID0:Fingerprint:Strong
+                String.format("%d:2:%d", fingerprintId, fingerprintStrength),
+                // ID2:Face:Convenience
+                String.format("%d:8:%d", faceId, faceStrength)
+        };
+
+        when(mInjector.getFingerprintConfiguration(any())).thenReturn(config);
+        when(mInjector.getFaceConfiguration(any())).thenReturn(config);
+        when(mInjector.getFingerprintAidlInstances()).thenReturn(new String[]{});
+        when(mInjector.getFaceAidlInstances()).thenReturn(new String[]{});
+
+        mAuthService = new AuthService(mContext, mInjector);
+        mAuthService.onStart();
+
+        verify(mFingerprintService).registerAuthenticatorsLegacy(
+                mFingerprintSensorConfigurationsCaptor.capture());
+
+        final SensorProps[] fingerprintProp = mFingerprintSensorConfigurationsCaptor.getValue()
+                .getSensorPairForInstance("defaultHIDL").second;
+
+        assertEquals(fingerprintProp[0].commonProps.sensorId, fingerprintId);
+        assertEquals(fingerprintProp[0].commonProps.sensorStrength,
+                Utils.authenticatorStrengthToPropertyStrength(fingerprintStrength));
+
+        verify(mFaceService).registerAuthenticatorsLegacy(
+                mFaceSensorConfigurationsCaptor.capture());
+
+        final android.hardware.biometrics.face.SensorProps[] faceProp =
+                mFaceSensorConfigurationsCaptor.getValue()
+                        .getSensorPairForInstance("defaultHIDL").second;
+
+        assertEquals(faceProp[0].commonProps.sensorId, faceId);
+        assertEquals(faceProp[0].commonProps.sensorStrength,
+                Utils.authenticatorStrengthToPropertyStrength(faceStrength));
+    }
 
     // TODO(b/141025588): Check that an exception is thrown when the userId != callingUserId
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
new file mode 100644
index 0000000..c9e1c4a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
@@ -0,0 +1,205 @@
+/*
+ * 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.biometrics.sensors.face;
+
+import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
+import static android.hardware.face.FaceSensorProperties.TYPE_UNKNOWN;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.face.SensorProps;
+import android.hardware.face.FaceSensorConfigurations;
+import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.provider.Settings;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
+import com.android.server.biometrics.Flags;
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.face.aidl.FaceProvider;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@Presubmit
+@SmallTest
+public class FaceServiceTest {
+    private static final int ID_DEFAULT = 2;
+    private static final int ID_VIRTUAL = 6;
+    private static final String NAME_DEFAULT = "default";
+    private static final String NAME_VIRTUAL = "virtual";
+
+    @Rule
+    public final MockitoRule mMockito = MockitoJUnit.rule();
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+    @Rule
+    public final FakeSettingsProviderRule mSettingsRule = FakeSettingsProvider.rule();
+    @Mock
+    FaceProvider mFaceProviderDefault;
+    @Mock
+    FaceProvider mFaceProviderVirtual;
+    @Mock
+    IFace mDefaultFaceDaemon;
+    @Mock
+    IFace mVirtualFaceDaemon;
+    @Mock
+    IBiometricService mIBiometricService;
+
+    private final SensorProps mDefaultSensorProps = new SensorProps();
+    private final SensorProps mVirtualSensorProps = new SensorProps();
+    private FaceService mFaceService;
+    private final FaceSensorPropertiesInternal mSensorPropsDefault =
+            new FaceSensorPropertiesInternal(ID_DEFAULT, STRENGTH_STRONG,
+                    2 /* maxEnrollmentsPerUser */,
+                    List.of(),
+                    TYPE_UNKNOWN,
+                    true /* supportsFaceDetection */,
+                    true /* supportsSelfIllumination */,
+                    false /* resetLockoutRequiresChallenge */);
+    private final FaceSensorPropertiesInternal mSensorPropsVirtual =
+            new FaceSensorPropertiesInternal(ID_VIRTUAL, STRENGTH_STRONG,
+                    2 /* maxEnrollmentsPerUser */,
+                    List.of(),
+                    TYPE_UNKNOWN,
+                    true /* supportsFaceDetection */,
+                    true /* supportsSelfIllumination */,
+                    false /* resetLockoutRequiresChallenge */);
+    private FaceSensorConfigurations mFaceSensorConfigurations;
+
+    @Before
+    public void setUp() throws RemoteException {
+        when(mDefaultFaceDaemon.getSensorProps()).thenReturn(
+                new SensorProps[]{mDefaultSensorProps});
+        when(mVirtualFaceDaemon.getSensorProps()).thenReturn(
+                new SensorProps[]{mVirtualSensorProps});
+        when(mFaceProviderDefault.getSensorProperties()).thenReturn(List.of(mSensorPropsDefault));
+        when(mFaceProviderVirtual.getSensorProperties()).thenReturn(List.of(mSensorPropsVirtual));
+
+        mFaceSensorConfigurations = new FaceSensorConfigurations(false);
+        mFaceSensorConfigurations.addAidlConfigs(new String[]{NAME_DEFAULT, NAME_VIRTUAL},
+                (name) -> {
+                    if (name.equals(IFace.DESCRIPTOR + "/" + NAME_DEFAULT)) {
+                        return mDefaultFaceDaemon;
+                    } else if (name.equals(IFace.DESCRIPTOR + "/" + NAME_VIRTUAL)) {
+                        return mVirtualFaceDaemon;
+                    }
+                    return null;
+                });
+    }
+
+    private void initService() {
+        mFaceService = new FaceService(mContext,
+                (filteredSensorProps, resetLockoutRequiresChallenge) -> {
+                    if (NAME_DEFAULT.equals(filteredSensorProps.first)) return mFaceProviderDefault;
+                    if (NAME_VIRTUAL.equals(filteredSensorProps.first)) return mFaceProviderVirtual;
+                    return null;
+                }, () -> mIBiometricService);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void registerAuthenticatorsLegacy_defaultOnly() throws Exception {
+        initService();
+
+        mFaceService.mServiceWrapper.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+        waitForRegistration();
+
+        verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT),
+                eq(BiometricAuthenticator.TYPE_FACE),
+                eq(Utils.propertyStrengthToAuthenticatorStrength(STRENGTH_STRONG)),
+                any());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void registerAuthenticatorsLegacy_virtualOnly() throws Exception {
+        initService();
+        Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
+                Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 1);
+
+        mFaceService.mServiceWrapper.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+        waitForRegistration();
+
+        verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL),
+                eq(BiometricAuthenticator.TYPE_FACE),
+                eq(Utils.propertyStrengthToAuthenticatorStrength(STRENGTH_STRONG)), any());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void registerAuthenticatorsLegacy_virtualAlwaysWhenNoOther() throws Exception {
+        mFaceSensorConfigurations = new FaceSensorConfigurations(false);
+        mFaceSensorConfigurations.addAidlConfigs(new String[]{NAME_VIRTUAL},
+                (name) -> {
+                    if (name.equals(IFace.DESCRIPTOR + "/" + NAME_DEFAULT)) {
+                        return mDefaultFaceDaemon;
+                    } else if (name.equals(IFace.DESCRIPTOR + "/" + NAME_VIRTUAL)) {
+                        return mVirtualFaceDaemon;
+                    }
+                    return null;
+                });
+        initService();
+
+        mFaceService.mServiceWrapper.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+        waitForRegistration();
+
+        verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL),
+                eq(BiometricAuthenticator.TYPE_FACE),
+                eq(Utils.propertyStrengthToAuthenticatorStrength(STRENGTH_STRONG)), any());
+    }
+
+    private void waitForRegistration() throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        mFaceService.mServiceWrapper.addAuthenticatorsRegisteredCallback(
+                new IFaceAuthenticatorsRegisteredCallback.Stub() {
+                    public void onAllAuthenticatorsRegistered(
+                            List<FaceSensorPropertiesInternal> sensors) {
+                        latch.countDown();
+                    }
+                });
+        latch.await(10, TimeUnit.SECONDS);
+    }
+}
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 f43120d..8b1a291 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
@@ -16,6 +16,9 @@
 
 package com.android.server.biometrics.sensors.face.aidl;
 
+import static android.os.UserHandle.USER_NULL;
+import static android.os.UserHandle.USER_SYSTEM;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
@@ -32,15 +35,19 @@
 import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.ISession;
 import android.hardware.biometrics.face.SensorProps;
+import android.hardware.face.HidlFaceSensorConfig;
 import android.os.RemoteException;
-import android.os.UserHandle;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
@@ -49,6 +56,7 @@
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -59,6 +67,10 @@
 @SmallTest
 public class FaceProviderTest {
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
     private static final String TAG = "FaceProviderTest";
 
     private static final float FRR_THRESHOLD = 0.2f;
@@ -109,7 +121,7 @@
 
         mFaceProvider = new FaceProvider(mContext, mBiometricStateCallback,
                 mSensorProps, TAG, mLockoutResetDispatcher, mBiometricContext,
-                mDaemon);
+                mDaemon, false /* resetLockoutRequiresChallenge */, false /* testHalEnabled */);
     }
 
     @Test
@@ -124,10 +136,38 @@
 
             assertThat(currentClient).isInstanceOf(FaceInternalCleanupClient.class);
             assertThat(currentClient.getSensorId()).isEqualTo(prop.commonProps.sensorId);
-            assertThat(currentClient.getTargetUserId()).isEqualTo(UserHandle.USER_SYSTEM);
+            assertThat(currentClient.getTargetUserId()).isEqualTo(USER_SYSTEM);
         }
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void testAddingHidlSensors() {
+        when(mResources.getIntArray(anyInt())).thenReturn(new int[]{});
+        when(mResources.getBoolean(anyInt())).thenReturn(false);
+
+        final int faceId = 0;
+        final int faceStrength = 15;
+        final String config = String.format("%d:8:%d", faceId, faceStrength);
+        final HidlFaceSensorConfig faceSensorConfig = new HidlFaceSensorConfig();
+        faceSensorConfig.parse(config, mContext);
+        final HidlFaceSensorConfig[] hidlFaceSensorConfig =
+                new HidlFaceSensorConfig[]{faceSensorConfig};
+        mFaceProvider = new FaceProvider(mContext,
+                mBiometricStateCallback, hidlFaceSensorConfig, TAG,
+                mLockoutResetDispatcher, mBiometricContext, mDaemon,
+                true /* resetLockoutRequiresChallenge */,
+                true /* testHalEnabled */);
+
+        assertThat(mFaceProvider.mFaceSensors.get(faceId)
+                .getLazySession().get().getUserId()).isEqualTo(USER_NULL);
+
+        waitForIdle();
+
+        assertThat(mFaceProvider.mFaceSensors.get(faceId)
+                .getLazySession().get().getUserId()).isEqualTo(USER_SYSTEM);
+    }
+
     @SuppressWarnings("rawtypes")
     @Test
     public void halServiceDied_resetsAllSchedulers() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
index 7a293e8..e7f7195 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
@@ -157,7 +157,7 @@
                 sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */);
         final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext,
                 null /* handler */, internalProp, mLockoutResetDispatcher, mBiometricContext);
-
+        sensor.init(mLockoutResetDispatcher, mFaceProvider);
         mScheduler.reset();
 
         assertNull(mScheduler.getCurrentClient());
@@ -185,6 +185,7 @@
                 sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */);
         final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, null,
                 internalProp, mLockoutResetDispatcher, mBiometricContext, mCurrentSession);
+        sensor.init(mLockoutResetDispatcher, mFaceProvider);
         mScheduler = (UserAwareBiometricScheduler) sensor.getScheduler();
         sensor.mCurrentSession = new AidlSession(0, mock(ISession.class),
                 USER_ID, mHalCallback);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java
new file mode 100644
index 0000000..4e43332
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java
@@ -0,0 +1,235 @@
+/*
+ * 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.biometrics.sensors.face.hidl;
+
+import static com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter.ENROLL_TIMEOUT_SEC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.hardware.biometrics.face.V1_0.OptionalUint64;
+import android.hardware.biometrics.face.V1_0.Status;
+import android.hardware.face.Face;
+import android.hardware.face.HidlFaceSensorConfig;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+import android.testing.TestableContext;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.face.aidl.FaceEnrollClient;
+import com.android.server.biometrics.sensors.face.aidl.FaceProvider;
+import com.android.server.biometrics.sensors.face.aidl.FaceResetLockoutClient;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+public class HidlToAidlSensorAdapterTest {
+    private static final String TAG = "HidlToAidlSensorAdapterTest";
+    private static final int USER_ID = 2;
+    private static final int SENSOR_ID = 4;
+    private static final byte[] HAT = new byte[69];
+    @Rule
+    public final MockitoRule mMockito = MockitoJUnit.rule();
+
+    @Mock
+    private IBiometricService mBiometricService;
+    @Mock
+    private LockoutResetDispatcher mLockoutResetDispatcherForSensor;
+    @Mock
+    private LockoutResetDispatcher mLockoutResetDispatcherForClient;
+    @Mock
+    private BiometricLogger mLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+    @Mock
+    private AuthSessionCoordinator mAuthSessionCoordinator;
+    @Mock
+    private FaceProvider mFaceProvider;
+    @Mock
+    private Runnable mInternalCleanupAndGetFeatureRunnable;
+    @Mock
+    private IBiometricsFace mDaemon;
+    @Mock
+    AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+    @Mock
+    BiometricUtils<Face> mBiometricUtils;
+
+    private final TestLooper mLooper = new TestLooper();
+    private HidlToAidlSensorAdapter mHidlToAidlSensorAdapter;
+    private final TestableContext mContext = new TestableContext(
+            ApplicationProvider.getApplicationContext());
+
+    @Before
+    public void setUp() throws RemoteException {
+        final OptionalUint64 result = new OptionalUint64();
+        result.status = Status.OK;
+
+        when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
+        when(mDaemon.setCallback(any())).thenReturn(result);
+        doAnswer((answer) -> {
+            mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback()
+                    .onLockoutChanged(0);
+            return null;
+        }).when(mDaemon).resetLockout(any());
+        doAnswer((answer) -> {
+            mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback()
+                    .onEnrollmentProgress(1, 0);
+            return null;
+        }).when(mDaemon).enroll(any(), anyInt(), any());
+
+        mContext.getOrCreateTestableResources();
+
+        final String config = String.format("%d:8:15", SENSOR_ID);
+        final BiometricScheduler scheduler = new BiometricScheduler(TAG,
+                new Handler(mLooper.getLooper()),
+                BiometricScheduler.SENSOR_TYPE_FACE,
+                null /* gestureAvailabilityTracker */,
+                mBiometricService, 10 /* recentOperationsLimit */);
+        final HidlFaceSensorConfig faceSensorConfig = new HidlFaceSensorConfig();
+        faceSensorConfig.parse(config, mContext);
+        mHidlToAidlSensorAdapter = new HidlToAidlSensorAdapter(TAG, mFaceProvider,
+                mContext, new Handler(mLooper.getLooper()), faceSensorConfig,
+                mLockoutResetDispatcherForSensor, mBiometricContext,
+                false /* resetLockoutRequiresChallenge */, mInternalCleanupAndGetFeatureRunnable,
+                mAuthSessionCoordinator, mDaemon, mAidlResponseHandlerCallback);
+        mHidlToAidlSensorAdapter.init(mLockoutResetDispatcherForSensor, mFaceProvider);
+        mHidlToAidlSensorAdapter.setScheduler(scheduler);
+        mHidlToAidlSensorAdapter.handleUserChanged(USER_ID);
+    }
+
+    @Test
+    public void lockoutTimedResetViaClient() {
+        setLockoutTimed();
+
+        mHidlToAidlSensorAdapter.getScheduler().scheduleClientMonitor(
+                new FaceResetLockoutClient(mContext, mHidlToAidlSensorAdapter.getLazySession(),
+                        USER_ID, TAG, SENSOR_ID, mLogger, mBiometricContext, HAT,
+                        mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */),
+                        mLockoutResetDispatcherForClient, 0 /* biometricStrength */));
+        mLooper.dispatchAll();
+
+        assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false/* forAuth */)
+                .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+        verify(mLockoutResetDispatcherForClient, never()).notifyLockoutResetCallbacks(SENSOR_ID);
+        verify(mLockoutResetDispatcherForSensor).notifyLockoutResetCallbacks(SENSOR_ID);
+        verify(mAuthSessionCoordinator, never()).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+    }
+
+    @Test
+    public void lockoutTimedResetViaCallback() {
+        setLockoutTimed();
+
+        mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback().onLockoutChanged(0);
+        mLooper.dispatchAll();
+
+        verify(mLockoutResetDispatcherForSensor).notifyLockoutResetCallbacks(eq(
+                SENSOR_ID));
+        assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+                .getLockoutModeForUser(USER_ID))
+                .isEqualTo(LockoutTracker.LOCKOUT_NONE);
+        verify(mAuthSessionCoordinator, never()).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+    }
+
+    @Test
+    public void lockoutPermanentResetViaCallback() {
+        setLockoutPermanent();
+
+        mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback().onLockoutChanged(0);
+        mLooper.dispatchAll();
+
+        verify(mLockoutResetDispatcherForSensor).notifyLockoutResetCallbacks(eq(
+                SENSOR_ID));
+        assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+                .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+        verify(mAuthSessionCoordinator, never()).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+    }
+
+    @Test
+    public void lockoutPermanentResetViaClient() {
+        setLockoutPermanent();
+
+        mHidlToAidlSensorAdapter.getScheduler().scheduleClientMonitor(
+                new FaceResetLockoutClient(mContext,
+                        mHidlToAidlSensorAdapter.getLazySession(),
+                        USER_ID, TAG, SENSOR_ID, mLogger, mBiometricContext, HAT,
+                        mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */),
+                        mLockoutResetDispatcherForClient, 0 /* biometricStrength */));
+        mLooper.dispatchAll();
+
+        verify(mLockoutResetDispatcherForClient, never()).notifyLockoutResetCallbacks(SENSOR_ID);
+        verify(mLockoutResetDispatcherForSensor).notifyLockoutResetCallbacks(SENSOR_ID);
+        assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+                .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+        verify(mAuthSessionCoordinator, never()).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+    }
+
+    @Test
+    public void verifyOnEnrollSuccessCallback() {
+        mHidlToAidlSensorAdapter.getScheduler().scheduleClientMonitor(new FaceEnrollClient(mContext,
+                mHidlToAidlSensorAdapter.getLazySession(), null /* token */, null /* listener */,
+                USER_ID, HAT, TAG, 1 /* requestId */, mBiometricUtils,
+                new int[]{} /* disabledFeatures */, ENROLL_TIMEOUT_SEC, null /* previewSurface */,
+                SENSOR_ID, mLogger, mBiometricContext, 1 /* maxTemplatesPerUser */,
+                false /* debugConsent */));
+        mLooper.dispatchAll();
+
+        verify(mAidlResponseHandlerCallback).onEnrollSuccess();
+    }
+
+    private void setLockoutTimed() {
+        mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback()
+                .onLockoutChanged(1);
+        mLooper.dispatchAll();
+
+        assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+                .getLockoutModeForUser(USER_ID))
+                .isEqualTo(LockoutTracker.LOCKOUT_TIMED);
+    }
+
+    private void setLockoutPermanent() {
+        mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback()
+                .onLockoutChanged(-1);
+        mLooper.dispatchAll();
+
+        assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+                .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_PERMANENT);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapterTest.java
similarity index 80%
rename from services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java
rename to services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapterTest.java
index 9a40e8a..b9a4fb4 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapterTest.java
@@ -16,7 +16,7 @@
 
 package com.android.server.biometrics.sensors.face.hidl;
 
-import static com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter.ENROLL_TIMEOUT_SEC;
+import static com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter.ENROLL_TIMEOUT_SEC;
 import static com.android.server.biometrics.sensors.face.hidl.FaceGenerateChallengeClient.CHALLENGE_TIMEOUT_SEC;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -68,7 +68,7 @@
 
 @Presubmit
 @SmallTest
-public class AidlToHidlAdapterTest {
+public class HidlToAidlSessionAdapterTest {
     @Rule
     public final MockitoRule mockito = MockitoJUnit.rule();
 
@@ -84,25 +84,32 @@
     private Clock mClock;
 
     private final long mChallenge = 100L;
-    private AidlToHidlAdapter mAidlToHidlAdapter;
+    private HidlToAidlSessionAdapter mHidlToAidlSessionAdapter;
     private final Face mFace = new Face("face" /* name */, 1 /* faceId */, 0 /* deviceId */);
     private final int mFeature = BiometricFaceConstants.FEATURE_REQUIRE_REQUIRE_DIVERSITY;
     private final byte[] mFeatures = new byte[]{Feature.REQUIRE_ATTENTION};
 
     @Before
     public void setUp() throws RemoteException {
+        final OptionalUint64 setCallbackResult = new OptionalUint64();
+        setCallbackResult.value = 1;
+
+        when(mSession.setCallback(any())).thenReturn(setCallbackResult);
+
         TestableContext testableContext = new TestableContext(
                 InstrumentationRegistry.getInstrumentation().getContext());
         testableContext.addMockSystemService(FaceManager.class, mFaceManager);
-        mAidlToHidlAdapter = new AidlToHidlAdapter(testableContext, () -> mSession, 0 /* userId */,
-                mAidlResponseHandler, mClock);
+
+        mHidlToAidlSessionAdapter = new HidlToAidlSessionAdapter(testableContext, () -> mSession,
+                0 /* userId */, mAidlResponseHandler, mClock);
         mHardwareAuthToken.timestamp = new Timestamp();
         mHardwareAuthToken.mac = new byte[10];
-        final OptionalUint64 result = new OptionalUint64();
-        result.status = Status.OK;
-        result.value = mChallenge;
 
-        when(mSession.generateChallenge(anyInt())).thenReturn(result);
+        final OptionalUint64 generateChallengeResult = new OptionalUint64();
+        generateChallengeResult.status = Status.OK;
+        generateChallengeResult.value = mChallenge;
+
+        when(mSession.generateChallenge(anyInt())).thenReturn(generateChallengeResult);
         when(mFaceManager.getEnrolledFaces(anyInt())).thenReturn(List.of(mFace));
     }
 
@@ -112,16 +119,16 @@
 
         final ArgumentCaptor<Long> challengeCaptor = ArgumentCaptor.forClass(Long.class);
 
-        mAidlToHidlAdapter.generateChallenge();
+        mHidlToAidlSessionAdapter.generateChallenge();
 
         verify(mSession).generateChallenge(CHALLENGE_TIMEOUT_SEC);
         verify(mAidlResponseHandler).onChallengeGenerated(challengeCaptor.capture());
         assertThat(challengeCaptor.getValue()).isEqualTo(mChallenge);
 
         forwardTime(10 /* seconds */);
-        mAidlToHidlAdapter.generateChallenge();
+        mHidlToAidlSessionAdapter.generateChallenge();
         forwardTime(20 /* seconds */);
-        mAidlToHidlAdapter.generateChallenge();
+        mHidlToAidlSessionAdapter.generateChallenge();
 
         //Confirms that the challenge is cached and the hal method is not called again
         verifyNoMoreInteractions(mSession);
@@ -129,7 +136,7 @@
                 .onChallengeGenerated(mChallenge);
 
         forwardTime(60 /* seconds */);
-        mAidlToHidlAdapter.generateChallenge();
+        mHidlToAidlSessionAdapter.generateChallenge();
 
         //HAL method called after challenge has timed out
         verify(mSession, times(2)).generateChallenge(CHALLENGE_TIMEOUT_SEC);
@@ -138,11 +145,11 @@
     @Test
     public void testRevokeChallenge_waitsUntilEmpty() throws RemoteException {
         for (int i = 0; i < 3; i++) {
-            mAidlToHidlAdapter.generateChallenge();
+            mHidlToAidlSessionAdapter.generateChallenge();
             forwardTime(10 /* seconds */);
         }
         for (int i = 0; i < 3; i++) {
-            mAidlToHidlAdapter.revokeChallenge(0);
+            mHidlToAidlSessionAdapter.revokeChallenge(0);
             forwardTime((i + 1) * 10 /* seconds */);
         }
 
@@ -151,20 +158,19 @@
 
     @Test
     public void testRevokeChallenge_timeout() throws RemoteException {
-        mAidlToHidlAdapter.generateChallenge();
-        mAidlToHidlAdapter.generateChallenge();
+        mHidlToAidlSessionAdapter.generateChallenge();
+        mHidlToAidlSessionAdapter.generateChallenge();
         forwardTime(700);
-        mAidlToHidlAdapter.generateChallenge();
-        mAidlToHidlAdapter.revokeChallenge(0);
+        mHidlToAidlSessionAdapter.generateChallenge();
+        mHidlToAidlSessionAdapter.revokeChallenge(0);
 
         verify(mSession).revokeChallenge();
     }
 
     @Test
     public void testEnroll() throws RemoteException {
-        ICancellationSignal cancellationSignal = mAidlToHidlAdapter.enroll(mHardwareAuthToken,
-                EnrollmentType.DEFAULT, mFeatures,
-                null /* previewSurface */);
+        ICancellationSignal cancellationSignal = mHidlToAidlSessionAdapter.enroll(
+                mHardwareAuthToken, EnrollmentType.DEFAULT, mFeatures, null /* previewSurface */);
         ArgumentCaptor<ArrayList<Integer>> featureCaptor = ArgumentCaptor.forClass(ArrayList.class);
 
         verify(mSession).enroll(any(), eq(ENROLL_TIMEOUT_SEC), featureCaptor.capture());
@@ -182,7 +188,8 @@
     @Test
     public void testAuthenticate() throws RemoteException {
         final int operationId = 2;
-        ICancellationSignal cancellationSignal = mAidlToHidlAdapter.authenticate(operationId);
+        ICancellationSignal cancellationSignal = mHidlToAidlSessionAdapter.authenticate(
+                operationId);
 
         verify(mSession).authenticate(operationId);
 
@@ -193,7 +200,7 @@
 
     @Test
     public void testDetectInteraction() throws RemoteException {
-        ICancellationSignal cancellationSignal = mAidlToHidlAdapter.detectInteraction();
+        ICancellationSignal cancellationSignal = mHidlToAidlSessionAdapter.detectInteraction();
 
         verify(mSession).authenticate(0);
 
@@ -204,7 +211,7 @@
 
     @Test
     public void testEnumerateEnrollments() throws RemoteException {
-        mAidlToHidlAdapter.enumerateEnrollments();
+        mHidlToAidlSessionAdapter.enumerateEnrollments();
 
         verify(mSession).enumerate();
     }
@@ -212,7 +219,7 @@
     @Test
     public void testRemoveEnrollment() throws RemoteException {
         final int[] enrollments = new int[]{1};
-        mAidlToHidlAdapter.removeEnrollments(enrollments);
+        mHidlToAidlSessionAdapter.removeEnrollments(enrollments);
 
         verify(mSession).remove(enrollments[0]);
     }
@@ -226,8 +233,8 @@
 
         when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result);
 
-        mAidlToHidlAdapter.setFeature(mFeature);
-        mAidlToHidlAdapter.getFeatures();
+        mHidlToAidlSessionAdapter.setFeature(mFeature);
+        mHidlToAidlSessionAdapter.getFeatures();
 
         verify(mSession).getFeature(eq(mFeature), anyInt());
         verify(mAidlResponseHandler).onFeaturesRetrieved(featureRetrieved.capture());
@@ -244,8 +251,8 @@
 
         when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result);
 
-        mAidlToHidlAdapter.setFeature(mFeature);
-        mAidlToHidlAdapter.getFeatures();
+        mHidlToAidlSessionAdapter.setFeature(mFeature);
+        mHidlToAidlSessionAdapter.getFeatures();
 
         verify(mSession).getFeature(eq(mFeature), anyInt());
         verify(mAidlResponseHandler).onFeaturesRetrieved(featureRetrieved.capture());
@@ -260,8 +267,8 @@
 
         when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result);
 
-        mAidlToHidlAdapter.setFeature(mFeature);
-        mAidlToHidlAdapter.getFeatures();
+        mHidlToAidlSessionAdapter.setFeature(mFeature);
+        mHidlToAidlSessionAdapter.getFeatures();
 
         verify(mSession).getFeature(eq(mFeature), anyInt());
         verify(mAidlResponseHandler, never()).onFeaturesRetrieved(any());
@@ -270,7 +277,7 @@
 
     @Test
     public void testGetFeatures_featureNotSet() throws RemoteException {
-        mAidlToHidlAdapter.getFeatures();
+        mHidlToAidlSessionAdapter.getFeatures();
 
         verify(mSession, never()).getFeature(eq(mFeature), anyInt());
         verify(mAidlResponseHandler, never()).onFeaturesRetrieved(any());
@@ -283,7 +290,7 @@
 
         when(mSession.setFeature(anyInt(), anyBoolean(), any(), anyInt())).thenReturn(Status.OK);
 
-        mAidlToHidlAdapter.setFeature(mHardwareAuthToken, feature, enabled);
+        mHidlToAidlSessionAdapter.setFeature(mHardwareAuthToken, feature, enabled);
 
         verify(mAidlResponseHandler).onFeatureSet(feature);
     }
@@ -296,7 +303,7 @@
         when(mSession.setFeature(anyInt(), anyBoolean(), any(), anyInt()))
                 .thenReturn(Status.INTERNAL_ERROR);
 
-        mAidlToHidlAdapter.setFeature(mHardwareAuthToken, feature, enabled);
+        mHidlToAidlSessionAdapter.setFeature(mHardwareAuthToken, feature, enabled);
 
         verify(mAidlResponseHandler).onError(BiometricFaceConstants.FACE_ERROR_UNKNOWN,
                 0 /* vendorCode */);
@@ -311,7 +318,7 @@
 
         when(mSession.getAuthenticatorId()).thenReturn(result);
 
-        mAidlToHidlAdapter.getAuthenticatorId();
+        mHidlToAidlSessionAdapter.getAuthenticatorId();
 
         verify(mSession).getAuthenticatorId();
         verify(mAidlResponseHandler).onAuthenticatorIdRetrieved(authenticatorId);
@@ -319,7 +326,7 @@
 
     @Test
     public void testResetLockout() throws RemoteException {
-        mAidlToHidlAdapter.resetLockout(mHardwareAuthToken);
+        mHidlToAidlSessionAdapter.resetLockout(mHardwareAuthToken);
 
         ArgumentCaptor<ArrayList> hatCaptor = ArgumentCaptor.forClass(ArrayList.class);
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
index 2aa62d9..f570ba2 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
@@ -42,13 +42,19 @@
 import android.app.AppOpsManager;
 import android.content.pm.PackageManager;
 import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.SensorProps;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
+import android.hardware.fingerprint.FingerprintSensorConfigurations;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.os.Binder;
 import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.provider.Settings;
 import android.testing.TestableContext;
 
@@ -58,6 +64,7 @@
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.util.test.FakeSettingsProviderRule;
 import com.android.server.LocalServices;
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider;
 import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
@@ -89,6 +96,9 @@
     @Rule
     public final MockitoRule mMockito = MockitoJUnit.rule();
     @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+    @Rule
     public final TestableContext mContext = new TestableContext(
             InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
     @Rule
@@ -110,6 +120,10 @@
     private IBinder mToken;
     @Mock
     private VirtualDeviceManagerInternal mVdmInternal;
+    @Mock
+    private IFingerprint mDefaultFingerprintDaemon;
+    @Mock
+    private IFingerprint mVirtualFingerprintDaemon;
 
     @Captor
     private ArgumentCaptor<FingerprintAuthenticateOptions> mAuthenticateOptionsCaptor;
@@ -126,7 +140,10 @@
                     List.of(),
                     TYPE_UDFPS_OPTICAL,
                     false /* resetLockoutRequiresHardwareAuthToken */);
+    private FingerprintSensorConfigurations mFingerprintSensorConfigurations;
     private FingerprintService mService;
+    private final SensorProps mDefaultSensorProps = new SensorProps();
+    private final SensorProps mVirtualSensorProps = new SensorProps();
 
     @Before
     public void setup() throws Exception {
@@ -139,6 +156,10 @@
                 .thenAnswer(i -> i.getArguments()[0].equals(ID_DEFAULT));
         when(mFingerprintVirtual.containsSensor(anyInt()))
                 .thenAnswer(i -> i.getArguments()[0].equals(ID_VIRTUAL));
+        when(mDefaultFingerprintDaemon.getSensorProps()).thenReturn(
+                new SensorProps[]{mDefaultSensorProps});
+        when(mVirtualFingerprintDaemon.getSensorProps()).thenReturn(
+                new SensorProps[]{mVirtualSensorProps});
 
         mContext.addMockSystemService(AppOpsManager.class, mAppOpsManager);
         for (int permission : List.of(OP_USE_BIOMETRIC, OP_USE_FINGERPRINT)) {
@@ -150,6 +171,18 @@
             mContext.getTestablePermissions().setPermission(
                     permission, PackageManager.PERMISSION_GRANTED);
         }
+
+        mFingerprintSensorConfigurations = new FingerprintSensorConfigurations(
+                true /* resetLockoutRequiresHardwareAuthToken */);
+        mFingerprintSensorConfigurations.addAidlSensors(new String[]{NAME_DEFAULT, NAME_VIRTUAL},
+                (name) -> {
+                    if (name.equals(IFingerprint.DESCRIPTOR + "/" + NAME_DEFAULT)) {
+                        return mDefaultFingerprintDaemon;
+                    } else if (name.equals(IFingerprint.DESCRIPTOR + "/" + NAME_VIRTUAL)) {
+                        return mVirtualFingerprintDaemon;
+                    }
+                    return null;
+                });
     }
 
     private void initServiceWith(String... aidlInstances) {
@@ -160,6 +193,10 @@
                     if (NAME_DEFAULT.equals(name)) return mFingerprintDefault;
                     if (NAME_VIRTUAL.equals(name)) return mFingerprintVirtual;
                     return null;
+                }, (sensorPropsPair, resetLockoutRequiresHardwareAuthToken) -> {
+                    if (NAME_DEFAULT.equals(sensorPropsPair.first)) return mFingerprintDefault;
+                    if (NAME_VIRTUAL.equals(sensorPropsPair.first)) return mFingerprintVirtual;
+                    return null;
                 });
     }
 
@@ -180,6 +217,17 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void registerAuthenticatorsLegacy_defaultOnly() throws Exception {
+        initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
+
+        mService.mServiceWrapper.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
+        waitForRegistration();
+
+        verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT), anyInt(), anyInt(), any());
+    }
+
+    @Test
     public void registerAuthenticators_virtualOnly() throws Exception {
         initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
         Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
@@ -192,6 +240,19 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void registerAuthenticatorsLegacy_virtualOnly() throws Exception {
+        initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
+        Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
+                Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 1);
+
+        mService.mServiceWrapper.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
+        waitForRegistration();
+
+        verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
+    }
+
+    @Test
     public void registerAuthenticators_virtualAlwaysWhenNoOther() throws Exception {
         initServiceWith(NAME_VIRTUAL);
 
@@ -201,6 +262,28 @@
         verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void registerAuthenticatorsLegacy_virtualAlwaysWhenNoOther() throws Exception {
+        mFingerprintSensorConfigurations =
+                new FingerprintSensorConfigurations(true);
+        mFingerprintSensorConfigurations.addAidlSensors(new String[]{NAME_VIRTUAL},
+                        (name) -> {
+                            if (name.equals(IFingerprint.DESCRIPTOR + "/" + NAME_DEFAULT)) {
+                                return mDefaultFingerprintDaemon;
+                            } else if (name.equals(IFingerprint.DESCRIPTOR + "/" + NAME_VIRTUAL)) {
+                                return mVirtualFingerprintDaemon;
+                            }
+                            return null;
+                        });
+        initServiceWith(NAME_VIRTUAL);
+
+        mService.mServiceWrapper.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
+        waitForRegistration();
+
+        verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
+    }
+
     private void waitForRegistration() throws Exception {
         final CountDownLatch latch = new CountDownLatch(1);
         mService.mServiceWrapper.addAuthenticatorsRegisteredCallback(
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index 4cfb83f..bf5986c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -16,6 +16,9 @@
 
 package com.android.server.biometrics.sensors.fingerprint.aidl;
 
+import static android.os.UserHandle.USER_NULL;
+import static android.os.UserHandle.USER_SYSTEM;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
@@ -34,17 +37,20 @@
 import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.biometrics.fingerprint.SensorLocation;
 import android.hardware.biometrics.fingerprint.SensorProps;
+import android.hardware.fingerprint.HidlFingerprintSensorConfig;
 import android.os.RemoteException;
-import android.os.UserHandle;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+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;
 import com.android.server.biometrics.sensors.HalClientMonitor;
@@ -52,6 +58,7 @@
 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -64,6 +71,10 @@
 
     private static final String TAG = "FingerprintProviderTest";
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Mock
     private Context mContext;
     @Mock
@@ -115,7 +126,8 @@
         mFingerprintProvider = new FingerprintProvider(mContext,
                 mBiometricStateCallback, mAuthenticationStateListeners, mSensorProps, TAG,
                 mLockoutResetDispatcher, mGestureAvailabilityDispatcher, mBiometricContext,
-                mDaemon);
+                mDaemon, false /* resetLockoutRequiresHardwareAuthToken */,
+                true /* testHalEnabled */);
     }
 
     @Test
@@ -123,17 +135,42 @@
         waitForIdle();
 
         for (SensorProps prop : mSensorProps) {
-            final BiometricScheduler scheduler =
-                    mFingerprintProvider.mFingerprintSensors.get(prop.commonProps.sensorId)
-                            .getScheduler();
-            BaseClientMonitor currentClient = scheduler.getCurrentClient();
-
-            assertThat(currentClient).isInstanceOf(FingerprintInternalCleanupClient.class);
-            assertThat(currentClient.getSensorId()).isEqualTo(prop.commonProps.sensorId);
-            assertThat(currentClient.getTargetUserId()).isEqualTo(UserHandle.USER_SYSTEM);
+            final Sensor sensor =
+                    mFingerprintProvider.mFingerprintSensors.get(prop.commonProps.sensorId);
+            assertThat(sensor.getLazySession().get().getUserId()).isEqualTo(USER_SYSTEM);
         }
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void testAddingHidlSensors() {
+        when(mResources.getIntArray(anyInt())).thenReturn(new int[]{});
+        when(mResources.getBoolean(anyInt())).thenReturn(false);
+
+        final int fingerprintId = 0;
+        final int fingerprintStrength = 15;
+        final String config = String.format("%d:2:%d", fingerprintId, fingerprintStrength);
+        final HidlFingerprintSensorConfig fingerprintSensorConfig =
+                new HidlFingerprintSensorConfig();
+        fingerprintSensorConfig.parse(config, mContext);
+        HidlFingerprintSensorConfig[] hidlFingerprintSensorConfigs =
+                new HidlFingerprintSensorConfig[]{fingerprintSensorConfig};
+        mFingerprintProvider = new FingerprintProvider(mContext,
+                mBiometricStateCallback, mAuthenticationStateListeners,
+                hidlFingerprintSensorConfigs, TAG, mLockoutResetDispatcher,
+                mGestureAvailabilityDispatcher, mBiometricContext, mDaemon,
+                false /* resetLockoutRequiresHardwareAuthToken */,
+                true /* testHalEnabled */);
+
+        assertThat(mFingerprintProvider.mFingerprintSensors.get(fingerprintId)
+                .getLazySession().get().getUserId()).isEqualTo(USER_NULL);
+
+        waitForIdle();
+
+        assertThat(mFingerprintProvider.mFingerprintSensors.get(fingerprintId)
+                .getLazySession().get().getUserId()).isEqualTo(USER_SYSTEM);
+    }
+
     @SuppressWarnings("rawtypes")
     @Test
     public void halServiceDied_resetsAllSchedulers() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
index 4102600..126a05e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
@@ -96,7 +96,7 @@
     private final TestLooper mLooper = new TestLooper();
     private final LockoutCache mLockoutCache = new LockoutCache();
 
-    private UserAwareBiometricScheduler mScheduler;
+    private BiometricScheduler mScheduler;
     private AidlResponseHandler mHalCallback;
 
     @Before
@@ -164,7 +164,8 @@
         final Sensor sensor = new Sensor("SensorTest", mFingerprintProvider, mContext,
                 null /* handler */, internalProp, mLockoutResetDispatcher,
                 mGestureAvailabilityDispatcher, mBiometricContext, mCurrentSession);
-        mScheduler = (UserAwareBiometricScheduler) sensor.getScheduler();
+        sensor.init(mGestureAvailabilityDispatcher, mLockoutResetDispatcher);
+        mScheduler = sensor.getScheduler();
         sensor.mCurrentSession = new AidlSession(0, mock(ISession.class),
                 USER_ID, mHalCallback);
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java
new file mode 100644
index 0000000..89a4961
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java
@@ -0,0 +1,301 @@
+/*
+ * 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.biometrics.sensors.fingerprint.hidl;
+
+import static android.hardware.fingerprint.FingerprintManager.ENROLL_ENROLL;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AlarmManager;
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.HidlFingerprintSensorConfig;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.annotation.NonNull;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.StartUserClient;
+import com.android.server.biometrics.sensors.StopUserClient;
+import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
+import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintEnrollClient;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintResetLockoutClient;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class HidlToAidlSensorAdapterTest {
+
+    private static final String TAG = "HidlToAidlSensorAdapterTest";
+    private static final int USER_ID = 2;
+    private static final int SENSOR_ID = 4;
+    private static final byte[] HAT = new byte[69];
+
+    @Rule
+    public final MockitoRule mMockito = MockitoJUnit.rule();
+
+    @Mock
+    private IBiometricService mBiometricService;
+    @Mock
+    private LockoutResetDispatcher mLockoutResetDispatcherForSensor;
+    @Mock
+    private LockoutResetDispatcher mLockoutResetDispatcherForClient;
+    @Mock
+    private BiometricLogger mLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+    @Mock
+    private AuthSessionCoordinator mAuthSessionCoordinator;
+    @Mock
+    private FingerprintProvider mFingerprintProvider;
+    @Mock
+    private GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
+    @Mock
+    private Runnable mInternalCleanupRunnable;
+    @Mock
+    private AlarmManager mAlarmManager;
+    @Mock
+    private IBiometricsFingerprint mDaemon;
+    @Mock
+    private AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+    @Mock
+    private BiometricUtils<Fingerprint> mBiometricUtils;
+    @Mock
+    private AuthenticationStateListeners mAuthenticationStateListeners;
+
+    private final TestLooper mLooper = new TestLooper();
+    private HidlToAidlSensorAdapter mHidlToAidlSensorAdapter;
+    private final TestableContext mContext = new TestableContext(
+            ApplicationProvider.getApplicationContext());
+
+    private final UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback =
+            new UserAwareBiometricScheduler.UserSwitchCallback() {
+                @NonNull
+                @Override
+                public StopUserClient<?> getStopUserClient(int userId) {
+                    return new StopUserClient<IBiometricsFingerprint>(mContext,
+                            mHidlToAidlSensorAdapter::getIBiometricsFingerprint, null, USER_ID,
+                            SENSOR_ID, mLogger, mBiometricContext, () -> {}) {
+                        @Override
+                        protected void startHalOperation() {
+                            getCallback().onClientFinished(this, true /* success */);
+                        }
+
+                        @Override
+                        public void unableToStart() {}
+                    };
+                }
+
+                @NonNull
+                @Override
+                public StartUserClient<?, ?> getStartUserClient(int newUserId) {
+                    return new StartUserClient<IBiometricsFingerprint, AidlSession>(mContext,
+                            mHidlToAidlSensorAdapter::getIBiometricsFingerprint, null,
+                            USER_ID, SENSOR_ID,
+                            mLogger, mBiometricContext,
+                            (newUserId1, newUser, halInterfaceVersion) ->
+                                    mHidlToAidlSensorAdapter.handleUserChanged(newUserId1)) {
+                        @Override
+                        public void start(@NonNull ClientMonitorCallback callback) {
+                            super.start(callback);
+                            startHalOperation();
+                        }
+
+                        @Override
+                        protected void startHalOperation() {
+                            mUserStartedCallback.onUserStarted(USER_ID, null, 0);
+                            getCallback().onClientFinished(this, true /* success */);
+                        }
+
+                        @Override
+                        public void unableToStart() {}
+                    };
+                }
+            };;
+
+    @Before
+    public void setUp() throws RemoteException {
+        when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
+        doAnswer((answer) -> {
+            mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback()
+                    .onEnrollmentProgress(1 /* enrollmentId */, 0 /* remaining */);
+            return null;
+        }).when(mDaemon).enroll(any(), anyInt(), anyInt());
+
+        mContext.addMockSystemService(AlarmManager.class, mAlarmManager);
+        mContext.getOrCreateTestableResources();
+
+        final String config = String.format("%d:2:15", SENSOR_ID);
+        final UserAwareBiometricScheduler scheduler = new UserAwareBiometricScheduler(TAG,
+                new Handler(mLooper.getLooper()),
+                BiometricScheduler.SENSOR_TYPE_FP_OTHER,
+                null /* gestureAvailabilityDispatcher */,
+                mBiometricService,
+                () -> USER_ID,
+                mUserSwitchCallback);
+        final HidlFingerprintSensorConfig fingerprintSensorConfig =
+                new HidlFingerprintSensorConfig();
+        fingerprintSensorConfig.parse(config, mContext);
+        mHidlToAidlSensorAdapter = new HidlToAidlSensorAdapter(TAG,
+                mFingerprintProvider, mContext, new Handler(mLooper.getLooper()),
+                fingerprintSensorConfig, mLockoutResetDispatcherForSensor,
+                mGestureAvailabilityDispatcher, mBiometricContext,
+                false /* resetLockoutRequiresHardwareAuthToken */,
+                mInternalCleanupRunnable, mAuthSessionCoordinator, mDaemon,
+                mAidlResponseHandlerCallback);
+        mHidlToAidlSensorAdapter.init(mGestureAvailabilityDispatcher,
+                mLockoutResetDispatcherForSensor);
+        mHidlToAidlSensorAdapter.setScheduler(scheduler);
+        mHidlToAidlSensorAdapter.handleUserChanged(USER_ID);
+    }
+
+    @Test
+    public void lockoutTimedResetViaClient() {
+        setLockoutTimed();
+
+        mHidlToAidlSensorAdapter.getScheduler().scheduleClientMonitor(
+                new FingerprintResetLockoutClient(mContext,
+                        mHidlToAidlSensorAdapter.getLazySession(),
+                        USER_ID, TAG, SENSOR_ID, mLogger, mBiometricContext, HAT,
+                        mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */),
+                        mLockoutResetDispatcherForClient, 0 /* biometricStrength */));
+        mLooper.dispatchAll();
+
+        verify(mAlarmManager).setExact(anyInt(), anyLong(), any());
+        verify(mLockoutResetDispatcherForClient).notifyLockoutResetCallbacks(SENSOR_ID);
+        verify(mLockoutResetDispatcherForSensor).notifyLockoutResetCallbacks(SENSOR_ID);
+        assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+                .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+        verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+    }
+
+    @Test
+    public void lockoutTimedResetViaCallback() {
+        setLockoutTimed();
+
+        mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback().onLockoutCleared();
+        mLooper.dispatchAll();
+
+        verify(mLockoutResetDispatcherForSensor, times(2)).notifyLockoutResetCallbacks(eq(
+                SENSOR_ID));
+        assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+                .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+        verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+    }
+
+    @Test
+    public void lockoutPermanentResetViaCallback() {
+        setLockoutPermanent();
+
+        mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback().onLockoutCleared();
+        mLooper.dispatchAll();
+
+        verify(mLockoutResetDispatcherForSensor, times(2)).notifyLockoutResetCallbacks(eq(
+                SENSOR_ID));
+        assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+                .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+        verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+    }
+
+    @Test
+    public void lockoutPermanentResetViaClient() {
+        setLockoutPermanent();
+
+        mHidlToAidlSensorAdapter.getScheduler().scheduleClientMonitor(
+                new FingerprintResetLockoutClient(mContext,
+                        mHidlToAidlSensorAdapter.getLazySession(),
+                        USER_ID, TAG, SENSOR_ID, mLogger, mBiometricContext, HAT,
+                        mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */),
+                        mLockoutResetDispatcherForClient, 0 /* biometricStrength */));
+        mLooper.dispatchAll();
+
+        verify(mAlarmManager, atLeast(1)).setExact(anyInt(), anyLong(), any());
+        verify(mLockoutResetDispatcherForClient).notifyLockoutResetCallbacks(SENSOR_ID);
+        verify(mLockoutResetDispatcherForSensor).notifyLockoutResetCallbacks(SENSOR_ID);
+        assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+                .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+        verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+    }
+
+    @Test
+    public void verifyOnEnrollSuccessCallback() {
+        mHidlToAidlSensorAdapter.getScheduler().scheduleClientMonitor(new FingerprintEnrollClient(
+                mContext, mHidlToAidlSensorAdapter.getLazySession(), null /* token */,
+                1 /* requestId */, null /* listener */, USER_ID, HAT, TAG, mBiometricUtils,
+                SENSOR_ID, mLogger, mBiometricContext,
+                mHidlToAidlSensorAdapter.getSensorProperties(), null, null,
+                mAuthenticationStateListeners, 5 /* maxTemplatesPerUser */, ENROLL_ENROLL));
+        mLooper.dispatchAll();
+
+        verify(mAidlResponseHandlerCallback).onEnrollSuccess();
+    }
+
+    private void setLockoutPermanent() {
+        for (int i = 0; i < 20; i++) {
+            mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+                    .addFailedAttemptForUser(USER_ID);
+        }
+
+        assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+                .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_PERMANENT);
+    }
+
+    private void setLockoutTimed() {
+        for (int i = 0; i < 5; i++) {
+            mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+                    .addFailedAttemptForUser(USER_ID);
+        }
+
+        assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+                .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_TIMED);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapterTest.java
similarity index 79%
rename from services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java
rename to services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapterTest.java
index b78ba82..d723e87 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapterTest.java
@@ -41,7 +41,7 @@
 
 @Presubmit
 @SmallTest
-public class AidlToHidlAdapterTest {
+public class HidlToAidlSessionAdapterTest {
     @Rule
     public final MockitoRule mockito = MockitoJUnit.rule();
 
@@ -54,11 +54,11 @@
 
     private final long mChallenge = 100L;
     private final int mUserId = 0;
-    private AidlToHidlAdapter mAidlToHidlAdapter;
+    private HidlToAidlSessionAdapter mHidlToAidlSessionAdapter;
 
     @Before
     public void setUp() {
-        mAidlToHidlAdapter = new AidlToHidlAdapter(() -> mSession, mUserId,
+        mHidlToAidlSessionAdapter = new HidlToAidlSessionAdapter(() -> mSession, mUserId,
                 mAidlResponseHandler);
         mHardwareAuthToken.timestamp = new Timestamp();
         mHardwareAuthToken.mac = new byte[10];
@@ -67,7 +67,7 @@
     @Test
     public void testGenerateChallenge() throws RemoteException {
         when(mSession.preEnroll()).thenReturn(mChallenge);
-        mAidlToHidlAdapter.generateChallenge();
+        mHidlToAidlSessionAdapter.generateChallenge();
 
         verify(mSession).preEnroll();
         verify(mAidlResponseHandler).onChallengeGenerated(mChallenge);
@@ -75,7 +75,7 @@
 
     @Test
     public void testRevokeChallenge() throws RemoteException {
-        mAidlToHidlAdapter.revokeChallenge(mChallenge);
+        mHidlToAidlSessionAdapter.revokeChallenge(mChallenge);
 
         verify(mSession).postEnroll();
         verify(mAidlResponseHandler).onChallengeRevoked(0L);
@@ -84,9 +84,9 @@
     @Test
     public void testEnroll() throws RemoteException {
         final ICancellationSignal cancellationSignal =
-                mAidlToHidlAdapter.enroll(mHardwareAuthToken);
+                mHidlToAidlSessionAdapter.enroll(mHardwareAuthToken);
 
-        verify(mSession).enroll(any(), anyInt(), eq(AidlToHidlAdapter.ENROLL_TIMEOUT_SEC));
+        verify(mSession).enroll(any(), anyInt(), eq(HidlToAidlSessionAdapter.ENROLL_TIMEOUT_SEC));
 
         cancellationSignal.cancel();
 
@@ -96,7 +96,8 @@
     @Test
     public void testAuthenticate() throws RemoteException {
         final int operationId = 2;
-        final ICancellationSignal cancellationSignal = mAidlToHidlAdapter.authenticate(operationId);
+        final ICancellationSignal cancellationSignal = mHidlToAidlSessionAdapter.authenticate(
+                operationId);
 
         verify(mSession).authenticate(operationId, mUserId);
 
@@ -107,7 +108,8 @@
 
     @Test
     public void testDetectInteraction() throws RemoteException {
-        final ICancellationSignal cancellationSignal = mAidlToHidlAdapter.detectInteraction();
+        final ICancellationSignal cancellationSignal = mHidlToAidlSessionAdapter
+                .detectInteraction();
 
         verify(mSession).authenticate(0 /* operationId */, mUserId);
 
@@ -118,7 +120,7 @@
 
     @Test
     public void testEnumerateEnrollments() throws RemoteException {
-        mAidlToHidlAdapter.enumerateEnrollments();
+        mHidlToAidlSessionAdapter.enumerateEnrollments();
 
         verify(mSession).enumerate();
     }
@@ -126,7 +128,7 @@
     @Test
     public void testRemoveEnrollment() throws RemoteException {
         final int[] enrollmentIds = new int[]{1};
-        mAidlToHidlAdapter.removeEnrollments(enrollmentIds);
+        mHidlToAidlSessionAdapter.removeEnrollments(enrollmentIds);
 
         verify(mSession).remove(mUserId, enrollmentIds[0]);
     }
@@ -134,14 +136,14 @@
     @Test
     public void testRemoveMultipleEnrollments() throws RemoteException {
         final int[] enrollmentIds = new int[]{1, 2};
-        mAidlToHidlAdapter.removeEnrollments(enrollmentIds);
+        mHidlToAidlSessionAdapter.removeEnrollments(enrollmentIds);
 
         verify(mSession).remove(mUserId, 0);
     }
 
     @Test
     public void testResetLockout() throws RemoteException {
-        mAidlToHidlAdapter.resetLockout(mHardwareAuthToken);
+        mHidlToAidlSessionAdapter.resetLockout(mHardwareAuthToken);
 
         verify(mAidlResponseHandler).onLockoutCleared();
     }
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 edfe1b4..071d571 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
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -24,10 +24,8 @@
 import static org.mockito.Mockito.when;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.companion.virtual.camera.VirtualCameraCallback;
 import android.companion.virtual.camera.VirtualCameraConfig;
-import android.companion.virtual.camera.VirtualCameraMetadata;
 import android.companion.virtual.camera.VirtualCameraStreamConfig;
 import android.companion.virtualcamera.IVirtualCameraService;
 import android.companion.virtualcamera.VirtualCameraConfiguration;
@@ -156,10 +154,6 @@
                     @NonNull VirtualCameraStreamConfig streamConfig) {}
 
             @Override
-            public void onProcessCaptureRequest(
-                    int streamId, long frameId, @Nullable VirtualCameraMetadata metadata) {}
-
-            @Override
             public void onStreamClosed(int streamId) {}
         };
     }
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
index b2854ce..ef15f60 100644
--- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
@@ -868,31 +868,6 @@
                 + "<compat-change id=\"1234\" name=\"MY_CHANGE1\" enableAfterTargetSdk=\"2\" />"
                 + "<compat-change id=\"1235\" name=\"MY_CHANGE2\" disabled=\"true\" />"
                 + "<compat-change id=\"1236\" name=\"MY_CHANGE3\" />"
-                + "</config>";
-
-        File dir = createTempDir();
-        writeToFile(dir, "platform_compat_config.xml", configXml);
-        CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext);
-        compatConfig.forceNonDebuggableFinalForTest(false);
-
-        compatConfig.initConfigFromLib(dir);
-
-        assertThat(compatConfig.isChangeEnabled(1234L,
-            ApplicationInfoBuilder.create().withTargetSdk(1).build())).isFalse();
-        assertThat(compatConfig.isChangeEnabled(1234L,
-            ApplicationInfoBuilder.create().withTargetSdk(3).build())).isTrue();
-        assertThat(compatConfig.isChangeEnabled(1235L,
-            ApplicationInfoBuilder.create().withTargetSdk(5).build())).isFalse();
-        assertThat(compatConfig.isChangeEnabled(1236L,
-            ApplicationInfoBuilder.create().withTargetSdk(1).build())).isTrue();
-    }
-
-    @Test
-    public void testReadApexConfig() throws IOException {
-        String configXml = "<config>"
-                + "<compat-change id=\"1234\" name=\"MY_CHANGE1\" enableAfterTargetSdk=\"2\" />"
-                + "<compat-change id=\"1235\" name=\"MY_CHANGE2\" disabled=\"true\" />"
-                + "<compat-change id=\"1236\" name=\"MY_CHANGE3\" />"
                 + "<compat-change id=\"1237\" name=\"MY_CHANGE4\" enableSinceTargetSdk=\"31\" />"
                 + "</config>";
 
@@ -912,6 +887,12 @@
         assertThat(compatConfig.isChangeEnabled(1236L,
             ApplicationInfoBuilder.create().withTargetSdk(1).build())).isTrue();
         assertThat(compatConfig.isChangeEnabled(1237L,
+            ApplicationInfoBuilder.create().withTargetSdk(31).build())).isFalse();
+
+        // Force the platform sdk version to be same as enabled target sdk
+        when(mBuildClassifier.platformTargetSdk()).thenReturn(31);
+
+        assertThat(compatConfig.isChangeEnabled(1237L,
             ApplicationInfoBuilder.create().withTargetSdk(31).build())).isTrue();
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
index 943a9c47..1dd64ff 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
@@ -18,7 +18,6 @@
 import static android.os.UserHandle.USER_SYSTEM;
 
 import static com.android.server.devicepolicy.DpmTestUtils.writeInputStreamToFile;
-import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -222,21 +221,21 @@
         prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.R);
 
         // Pretend some packages are suspended.
-        when(getServices().packageManagerInternal.isSuspendingAnyPackages(
-                PLATFORM_PACKAGE_NAME, USER_SYSTEM)).thenReturn(true);
+        when(getServices().packageManagerInternal.isAdminSuspendingAnyPackages(
+                USER_SYSTEM)).thenReturn(true);
 
         final DevicePolicyManagerServiceTestable dpms = bootDpmsUp();
 
         verify(getServices().packageManagerInternal, never())
-                .unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, USER_SYSTEM);
+                .unsuspendAdminSuspendedPackages(USER_SYSTEM);
 
         sendBroadcastWithUser(dpms, Intent.ACTION_USER_STARTED, USER_SYSTEM);
 
         // Verify that actual package suspension state is not modified after user start
         verify(getServices().packageManagerInternal, never())
-                .unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, USER_SYSTEM);
+                .unsuspendAdminSuspendedPackages(USER_SYSTEM);
         verify(getServices().ipackageManager, never()).setPackagesSuspendedAsUser(
-                any(), anyBoolean(), any(), any(), any(), anyInt(), any(), anyInt());
+                any(), anyBoolean(), any(), any(), any(), anyInt(), any(), anyInt(), anyInt());
 
         final DpmMockContext poContext = new DpmMockContext(getServices(), mRealTestContext);
         poContext.binder.callingUid = UserHandle.getUid(COPE_PROFILE_USER_ID, COPE_ADMIN1_APP_ID);
@@ -255,14 +254,14 @@
         prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.Q);
 
         // Pretend some packages are suspended.
-        when(getServices().packageManagerInternal.isSuspendingAnyPackages(
-                PLATFORM_PACKAGE_NAME, USER_SYSTEM)).thenReturn(true);
+        when(getServices().packageManagerInternal.isAdminSuspendingAnyPackages(
+                USER_SYSTEM)).thenReturn(true);
 
         final DevicePolicyManagerServiceTestable dpms = bootDpmsUp();
 
         // Verify that apps get unsuspended.
         verify(getServices().packageManagerInternal)
-                .unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, USER_SYSTEM);
+                .unsuspendAdminSuspendedPackages(USER_SYSTEM);
 
         final DpmMockContext poContext = new DpmMockContext(getServices(), mRealTestContext);
         poContext.binder.callingUid = UserHandle.getUid(COPE_PROFILE_USER_ID, COPE_ADMIN1_APP_ID);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index f4dac2c..2470403 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -63,7 +63,6 @@
 import static com.android.server.devicepolicy.DevicePolicyManagerService.ACTION_PROFILE_OFF_DEADLINE;
 import static com.android.server.devicepolicy.DevicePolicyManagerService.ACTION_TURN_PROFILE_ON_NOTIFICATION;
 import static com.android.server.devicepolicy.DpmMockContext.CALLER_USER_HANDLE;
-import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 import static com.android.server.testutils.TestUtils.assertExpectException;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -5080,7 +5079,7 @@
         verify(getServices().iwindowManager).refreshScreenCaptureDisabled();
         // Unsuspend personal apps
         verify(getServices().packageManagerInternal)
-                .unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, UserHandle.USER_SYSTEM);
+                .unsuspendAdminSuspendedPackages(UserHandle.USER_SYSTEM);
         verify(getServices().subscriptionManager).setSubscriptionUserHandle(0, null);
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
                 FLAG_ENABLE_WORK_PROFILE_TELEPHONY, "false", false);
@@ -7535,7 +7534,7 @@
                 .cancel(eq(SystemMessageProto.SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED));
         // Verify that the apps are NOT unsuspeded.
         verify(getServices().ipackageManager, never()).setPackagesSuspendedAsUser(
-                any(), eq(false), any(), any(), any(), anyInt(), any(), anyInt());
+                any(), eq(false), any(), any(), any(), anyInt(), any(), anyInt(), anyInt());
         // Verify that DPC is invoked to check policy compliance.
         verify(mContext.spiedContext).startActivityAsUser(
                 MockUtils.checkIntentAction(ACTION_CHECK_POLICY_COMPLIANCE),
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
index b4281d63c..101ea6d 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -183,6 +183,13 @@
     }
 
     @Test
+    public void testIsUserInitialized_NonExistentUserReturnsFalse() {
+        int nonExistentUserId = UserHandle.USER_NULL;
+        assertThat(mUserManagerService.isUserInitialized(nonExistentUserId))
+                .isFalse();
+    }
+
+    @Test
     public void testSetUserRestrictionWithIncorrectID() throws Exception {
         int incorrectId = 1;
         while (mUserManagerService.userExists(incorrectId)) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
index 526201f..670f9f6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -49,6 +49,7 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.SuspendDialogInfo;
 import android.content.pm.UserInfo;
+import android.content.pm.UserPackage;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -195,7 +196,7 @@
         mAInfo.applicationInfo.flags = FLAG_SUSPENDED;
 
         when(mPackageManagerInternal.getSuspendingPackage(TEST_PACKAGE_NAME, TEST_USER_ID))
-                .thenReturn(PLATFORM_PACKAGE_NAME);
+                .thenReturn(UserPackage.of(TEST_USER_ID, PLATFORM_PACKAGE_NAME));
 
         // THEN calling intercept returns true
         assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null));
@@ -227,9 +228,10 @@
                 .setMessage("Test Message")
                 .setIcon(0x11110001)
                 .build();
+        UserPackage suspender = UserPackage.of(TEST_USER_ID, suspendingPackage);
         when(mPackageManagerInternal.getSuspendingPackage(TEST_PACKAGE_NAME, TEST_USER_ID))
-                .thenReturn(suspendingPackage);
-        when(mPackageManagerInternal.getSuspendedDialogInfo(TEST_PACKAGE_NAME, suspendingPackage,
+                .thenReturn(suspender);
+        when(mPackageManagerInternal.getSuspendedDialogInfo(TEST_PACKAGE_NAME, suspender,
                 TEST_USER_ID)).thenReturn(dialogInfo);
         return dialogInfo;
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index d2c731c..ed99108 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -1087,4 +1087,16 @@
         assertTrue(homeActivity.getTask().isFocused());
         assertFalse(pinnedTask.isFocused());
     }
+
+    @Test
+    public void testContinueWindowLayout_notifyClientLifecycleManager() {
+        clearInvocations(mClientLifecycleManager);
+        mAtm.deferWindowLayout();
+
+        verify(mClientLifecycleManager, never()).onLayoutContinued();
+
+        mAtm.continueWindowLayout();
+
+        verify(mClientLifecycleManager).onLayoutContinued();
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
index 7fdc5fc..c757457 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
@@ -16,6 +16,8 @@
 
 package com.android.server.wm;
 
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -52,14 +54,12 @@
  * Build/Install/Run:
  *  atest WmTests:ClientLifecycleManagerTests
  */
-// Suppress GuardedBy warning on unit tests
-@SuppressWarnings("GuardedBy")
 @SmallTest
 @Presubmit
 public class ClientLifecycleManagerTests {
 
     @Rule(order = 0)
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
 
     @Rule(order = 1)
     public final SystemServicesTestRule mSystemServices = new SystemServicesTestRule();
@@ -225,4 +225,30 @@
         verify(mTransaction).schedule();
         verify(mTransaction).recycle();
     }
+
+    @Test
+    public void testLayoutDeferred() throws RemoteException {
+        mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+        spyOn(mWms.mWindowPlacerLocked);
+        doReturn(false).when(mWms.mWindowPlacerLocked).isInLayout();
+        doReturn(false).when(mWms.mWindowPlacerLocked).isTraversalScheduled();
+        doReturn(true).when(mWms.mWindowPlacerLocked).isLayoutDeferred();
+
+        // Queue transactions during layout deferred.
+        mLifecycleManager.scheduleTransactionItem(mNonBinderClient, mLifecycleItem);
+
+        verify(mLifecycleManager, never()).scheduleTransaction(any());
+
+        // Continue queueing when there are multi-level defer.
+        mLifecycleManager.onLayoutContinued();
+
+        verify(mLifecycleManager, never()).scheduleTransaction(any());
+
+        // Immediately dispatch when layout continue without ongoing/scheduled layout.
+        doReturn(false).when(mWms.mWindowPlacerLocked).isLayoutDeferred();
+
+        mLifecycleManager.onLayoutContinued();
+
+        verify(mLifecycleManager).scheduleTransaction(any());
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/CompatModePackagesTests.java b/services/tests/wmtests/src/com/android/server/wm/CompatModePackagesTests.java
index 1f7b65e..c187263 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CompatModePackagesTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CompatModePackagesTests.java
@@ -20,10 +20,13 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 
 import android.app.GameManagerInternal;
+import android.content.pm.ApplicationInfo;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
@@ -90,6 +93,14 @@
     public void testGetCompatScale_noGameManager() {
         assertEquals(mAtm.mCompatModePackages.getCompatScale(TEST_PACKAGE, TEST_USER_ID), 1f,
                 0.01f);
-    }
 
+        final ApplicationInfo info = new ApplicationInfo();
+        // Any non-zero value without FLAG_SUPPORTS_*_SCREENS.
+        info.flags = ApplicationInfo.FLAG_HAS_CODE;
+        info.packageName = info.sourceDir = "legacy.app";
+        mAtm.mCompatModePackages.compatibilityInfoForPackageLocked(info);
+        assertTrue(mAtm.mCompatModePackages.useLegacyScreenCompatMode(info.packageName));
+        mAtm.mCompatModePackages.handlePackageUninstalledLocked(info.packageName);
+        assertFalse(mAtm.mCompatModePackages.useLegacyScreenCompatMode(info.packageName));
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index c6796dc..985be42 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -19,6 +19,7 @@
 import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP;
 import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
 import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION;
+import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION_TO_USER;
 import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
 import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
 import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
@@ -673,6 +674,47 @@
     }
 
     @Test
+    @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER})
+    public void testOverrideOrientationIfNeeded_fullscreenOverrideEnabled_returnsUser()
+            throws Exception {
+        mDisplayContent.setIgnoreOrientationRequest(true);
+        assertEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER})
+    public void testOverrideOrientationIfNeeded_fullscreenOverrideEnabled_optOut_returnsUnchanged()
+            throws Exception {
+        mockThatProperty(PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE, /* value */ false);
+
+        mController = new LetterboxUiController(mWm, mActivity);
+        mDisplayContent.setIgnoreOrientationRequest(true);
+
+        assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER})
+    public void testOverrideOrientationIfNeeded_fullscreenOverrideEnabled_returnsUnchanged()
+            throws Exception {
+        mDisplayContent.setIgnoreOrientationRequest(false);
+        assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+             /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER})
+    public void testOverrideOrientationIfNeeded_fullscreenAndUserOverrideEnabled_returnsUnchanged()
+            throws Exception {
+        prepareActivityThatShouldApplyUserMinAspectRatioOverride();
+
+        assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+             /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
+    }
+
+    @Test
     @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT})
     public void testOverrideOrientationIfNeeded_portraitOverrideEnabled_returnsPortrait()
             throws Exception {
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 f99b489..8bf4833 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -499,11 +499,16 @@
     public void testAddWindowWithSubWindowTypeByWindowContext() {
         spyOn(mWm.mWindowContextListenerController);
 
-        final WindowToken windowToken = createTestWindowToken(TYPE_INPUT_METHOD, mDefaultDisplay);
-        final Session session = getTestSession();
+        final WindowState parentWin = createWindow(null, TYPE_INPUT_METHOD, "ime");
+        final IBinder parentToken = parentWin.mToken.token;
+        parentWin.mAttrs.token = parentToken;
+        mWm.mWindowMap.put(parentToken, parentWin);
+        final Session session = parentWin.mSession;
+        session.onWindowAdded(parentWin);
         final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                 TYPE_APPLICATION_ATTACHED_DIALOG);
-        params.token = windowToken.token;
+        params.token = parentToken;
+        params.setTitle("attached-dialog");
         final IBinder windowContextToken = new Binder();
         params.setWindowContextToken(windowContextToken);
         doReturn(true).when(mWm.mWindowContextListenerController)
@@ -517,6 +522,12 @@
 
         verify(mWm.mWindowContextListenerController, never()).registerWindowContainerListener(any(),
                 any(), any(), anyInt(), any(), anyBoolean());
+
+        assertTrue(parentWin.hasChild());
+        assertTrue(parentWin.isAttached());
+        session.binderDied();
+        assertFalse(parentWin.hasChild());
+        assertFalse(parentWin.isAttached());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index df4af11..616a23e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -294,7 +294,7 @@
      */
     static void suppressInsetsAnimation(InsetsControlTarget target) {
         spyOn(target);
-        Mockito.doNothing().when(target).notifyInsetsControlChanged();
+        Mockito.doNothing().when(target).notifyInsetsControlChanged(anyInt());
     }
 
     @After
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 72db7fe..ccd4ce0 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -2395,7 +2395,19 @@
                 return null;
             }
 
-            return queryEventsHelper(UserHandle.getCallingUserId(), query.getBeginTimeMillis(),
+            final int callingUserId = UserHandle.getCallingUserId();
+            int userId = query.getUserId();
+            if (userId == UserHandle.USER_NULL) {
+                // Convert userId to actual user Id if not specified in the query object.
+                userId = callingUserId;
+            }
+            if (userId != callingUserId) {
+                getContext().enforceCallingPermission(
+                        Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                        "No permission to query usage stats for user " + userId);
+            }
+
+            return queryEventsHelper(userId, query.getBeginTimeMillis(),
                     query.getEndTimeMillis(), callingPackage, query.getEventTypeFilter());
         }
 
diff --git a/services/voiceinteraction/Android.bp b/services/voiceinteraction/Android.bp
index de8d144..9e425b0 100644
--- a/services/voiceinteraction/Android.bp
+++ b/services/voiceinteraction/Android.bp
@@ -65,12 +65,14 @@
 
 java_library_static {
     name: "services.voiceinteraction",
-    defaults: ["platform_service_defaults"],
+    defaults: [
+        "platform_service_defaults",
+        "android.hardware.power-java_shared",
+    ],
     srcs: [":services.voiceinteraction-sources"],
     libs: [
         "services.core",
         "app-compat-annotations",
-        "android.hardware.power-V1-java",
         "android.hardware.power-V1.0-java",
     ],
 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
index 4720d27..f9fa9b7 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
@@ -106,96 +106,104 @@
             @Override
             public void onAttentionGained() {
                 Slog.v(TAG, "BinderCallback#onAttentionGained");
-                mEgressingData = true;
-                if (mAttentionListener == null) {
-                    return;
-                }
-                try {
-                    mAttentionListener.onAttentionGained();
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Error delivering attention gained event.", e);
-                    try {
-                        callback.onVisualQueryDetectionServiceFailure(
-                                new VisualQueryDetectionServiceFailure(
-                                        ERROR_CODE_ILLEGAL_ATTENTION_STATE,
-                                        "Attention listener failed to switch to GAINED state."));
-                    } catch (RemoteException ex) {
-                        Slog.v(TAG, "Fail to call onVisualQueryDetectionServiceFailure");
+                synchronized (mLock) {
+                    mEgressingData = true;
+                    if (mAttentionListener == null) {
+                        return;
                     }
-                    return;
+                    try {
+                        mAttentionListener.onAttentionGained();
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Error delivering attention gained event.", e);
+                        try {
+                            callback.onVisualQueryDetectionServiceFailure(
+                                    new VisualQueryDetectionServiceFailure(
+                                            ERROR_CODE_ILLEGAL_ATTENTION_STATE,
+                                            "Attention listener fails to switch to GAINED state."));
+                        } catch (RemoteException ex) {
+                            Slog.v(TAG, "Fail to call onVisualQueryDetectionServiceFailure");
+                        }
+                    }
                 }
             }
 
             @Override
             public void onAttentionLost() {
                 Slog.v(TAG, "BinderCallback#onAttentionLost");
-                mEgressingData = false;
-                if (mAttentionListener == null) {
-                    return;
-                }
-                try {
-                    mAttentionListener.onAttentionLost();
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Error delivering attention lost event.", e);
-                    try {
-                        callback.onVisualQueryDetectionServiceFailure(
-                                new VisualQueryDetectionServiceFailure(
-                                        ERROR_CODE_ILLEGAL_ATTENTION_STATE,
-                                        "Attention listener failed to switch to LOST state."));
-                    } catch (RemoteException ex) {
-                        Slog.v(TAG, "Fail to call onVisualQueryDetectionServiceFailure");
+                synchronized (mLock) {
+                    mEgressingData = false;
+                    if (mAttentionListener == null) {
+                        return;
                     }
-                    return;
+                    try {
+                        mAttentionListener.onAttentionLost();
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Error delivering attention lost event.", e);
+                        try {
+                            callback.onVisualQueryDetectionServiceFailure(
+                                    new VisualQueryDetectionServiceFailure(
+                                            ERROR_CODE_ILLEGAL_ATTENTION_STATE,
+                                            "Attention listener fails to switch to LOST state."));
+                        } catch (RemoteException ex) {
+                            Slog.v(TAG, "Fail to call onVisualQueryDetectionServiceFailure");
+                        }
+                    }
                 }
             }
 
             @Override
             public void onQueryDetected(@NonNull String partialQuery) throws RemoteException {
-                Objects.requireNonNull(partialQuery);
                 Slog.v(TAG, "BinderCallback#onQueryDetected");
-                if (!mEgressingData) {
-                    Slog.v(TAG, "Query should not be egressed within the unattention state.");
-                    callback.onVisualQueryDetectionServiceFailure(
-                            new VisualQueryDetectionServiceFailure(
-                                    ERROR_CODE_ILLEGAL_STREAMING_STATE,
-                                    "Cannot stream queries without attention signals."));
-                    return;
+                synchronized (mLock) {
+                    Objects.requireNonNull(partialQuery);
+                    if (!mEgressingData) {
+                        Slog.v(TAG, "Query should not be egressed within the unattention state.");
+                        callback.onVisualQueryDetectionServiceFailure(
+                                new VisualQueryDetectionServiceFailure(
+                                        ERROR_CODE_ILLEGAL_STREAMING_STATE,
+                                        "Cannot stream queries without attention signals."));
+                        return;
+                    }
+                    mQueryStreaming = true;
+                    callback.onQueryDetected(partialQuery);
+                    Slog.i(TAG, "Egressed from visual query detection process.");
                 }
-                mQueryStreaming = true;
-                callback.onQueryDetected(partialQuery);
-                Slog.i(TAG, "Egressed from visual query detection process.");
             }
 
             @Override
             public void onQueryFinished() throws RemoteException {
                 Slog.v(TAG, "BinderCallback#onQueryFinished");
-                if (!mQueryStreaming) {
-                    Slog.v(TAG, "Query streaming state signal FINISHED is block since there is"
-                            + " no active query being streamed.");
-                    callback.onVisualQueryDetectionServiceFailure(
-                            new VisualQueryDetectionServiceFailure(
-                                    ERROR_CODE_ILLEGAL_STREAMING_STATE,
-                                    "Cannot send FINISHED signal with no query streamed."));
-                    return;
+                synchronized (mLock) {
+                    if (!mQueryStreaming) {
+                        Slog.v(TAG, "Query streaming state signal FINISHED is block since there is"
+                                + " no active query being streamed.");
+                        callback.onVisualQueryDetectionServiceFailure(
+                                new VisualQueryDetectionServiceFailure(
+                                        ERROR_CODE_ILLEGAL_STREAMING_STATE,
+                                        "Cannot send FINISHED signal with no query streamed."));
+                        return;
+                    }
+                    callback.onQueryFinished();
+                    mQueryStreaming = false;
                 }
-                callback.onQueryFinished();
-                mQueryStreaming = false;
             }
 
             @Override
             public void onQueryRejected() throws RemoteException {
                 Slog.v(TAG, "BinderCallback#onQueryRejected");
-                if (!mQueryStreaming) {
-                    Slog.v(TAG, "Query streaming state signal REJECTED is block since there is"
-                            + " no active query being streamed.");
-                    callback.onVisualQueryDetectionServiceFailure(
-                            new VisualQueryDetectionServiceFailure(
-                                    ERROR_CODE_ILLEGAL_STREAMING_STATE,
-                                    "Cannot send REJECTED signal with no query streamed."));
-                    return;
+                synchronized (mLock) {
+                    if (!mQueryStreaming) {
+                        Slog.v(TAG, "Query streaming state signal REJECTED is block since there is"
+                                + " no active query being streamed.");
+                        callback.onVisualQueryDetectionServiceFailure(
+                                new VisualQueryDetectionServiceFailure(
+                                        ERROR_CODE_ILLEGAL_STREAMING_STATE,
+                                        "Cannot send REJECTED signal with no query streamed."));
+                        return;
+                    }
+                    callback.onQueryRejected();
+                    mQueryStreaming = false;
                 }
-                callback.onQueryRejected();
-                mQueryStreaming = false;
             }
         };
         return mRemoteDetectionService.run(
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index b167f1b..d05eb5c 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1349,6 +1349,24 @@
     }
 
     /**
+     * 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.
+     *
+     * @see #EXTRA_PHONE_ACCOUNT_HANDLE
+     * @return A list of {@code PhoneAccountHandle} objects.
+     *
+     * @throws IllegalStateException if telecom service is null.
+     */
+    @FlaggedApi(com.android.internal.telephony.flags.Flags.FLAG_WORK_PROFILE_API_SPLIT)
+    @RequiresPermission(allOf = {android.Manifest.permission.READ_PHONE_STATE,
+            android.Manifest.permission.INTERACT_ACROSS_PROFILES})
+    public @NonNull List<PhoneAccountHandle> getCallCapablePhoneAccountsAcrossProfiles() {
+        return getCallCapablePhoneAccountsAcrossProfiles(false);
+    }
+
+
+    /**
      * Returns a list of {@link PhoneAccountHandle}s for all self-managed
      * {@link ConnectionService}s owned by the calling {@link UserHandle}.
      * <p>
@@ -1423,7 +1441,7 @@
         if (service != null) {
             try {
                 return service.getCallCapablePhoneAccounts(includeDisabledAccounts,
-                        mContext.getOpPackageName(), mContext.getAttributionTag()).getList();
+                        mContext.getOpPackageName(), mContext.getAttributionTag(), false).getList();
             } catch (RemoteException e) {
                 Log.e(TAG, "Error calling ITelecomService#getCallCapablePhoneAccounts("
                         + includeDisabledAccounts + ")", e);
@@ -1433,6 +1451,37 @@
     }
 
     /**
+     * Returns a list of {@link PhoneAccountHandle}s visible to current user including those which
+     * have not been enabled by the user.
+     *
+     * @param includeDisabledAccounts When {@code true}, disabled phone accounts will be included,
+     *                                when {@code false}, only enabled phone accounts will be
+     *                                included.
+     * @return A list of {@code PhoneAccountHandle} objects.
+     *
+     * @throws IllegalStateException if telecom service is null.
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(com.android.internal.telephony.flags.Flags.FLAG_WORK_PROFILE_API_SPLIT)
+    @RequiresPermission(allOf = {android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+            android.Manifest.permission.INTERACT_ACROSS_PROFILES})
+    public @NonNull List<PhoneAccountHandle> getCallCapablePhoneAccountsAcrossProfiles(
+            boolean includeDisabledAccounts) {
+        ITelecomService service = getTelecomService();
+        if (service == null) {
+            throw new IllegalStateException("telecom service is null.");
+        }
+
+        try {
+            return service.getCallCapablePhoneAccounts(includeDisabledAccounts,
+                    mContext.getOpPackageName(), mContext.getAttributionTag(), true).getList();
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
      *  Returns a list of all {@link PhoneAccount}s registered for the calling package.
      *
      * @deprecated Use {@link #getSelfManagedPhoneAccounts()} instead to get only self-managed
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index 53154e6..f1bfd22 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -61,7 +61,8 @@
      * @see TelecomServiceImpl#getCallCapablePhoneAccounts
      */
     ParceledListSlice<PhoneAccountHandle> getCallCapablePhoneAccounts(
-            boolean includeDisabledAccounts, String callingPackage, String callingFeatureId);
+            boolean includeDisabledAccounts, String callingPackage,
+            String callingFeatureId, boolean acrossProfiles);
 
     /**
      * @see TelecomServiceImpl#getSelfManagedPhoneAccounts
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 7cb2cc3..bcd9929 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9577,14 +9577,20 @@
             "satellite_attach_supported_bool";
 
     /**
-     * The carrier-enabled satellite connection hysteresis time in seconds to determine whether to
-     * recommend Dialer to prompt users to use satellite emergency messaging.
+     * The carrier-enabled satellite connection hysteresis time in seconds for which the device
+     * continues in satellite mode after it loses the connection with the satellite network.
      * <p>
-     * A timer is started when there is an ongoing emergency call, and the IMS is not registered,
-     * and cellular service is not available, and the device was connected to a satellite network
-     * within this time in the past. When the timer expires, Telephony will send the event
+     * If the device is in satellite mode, the following actions will be taken by the device:
+     * <ul>
+     * <li>System UI will continue showing the satellite icon.</li>
+     * <li>When there is an ongoing emergency call, and the IMS is not registered, and cellular
+     * service is not available, and the device is in satellite mode, a timer with a duration
+     * defined by the overlay config
+     * {@code config_emergency_call_wait_for_connection_timeout_millis} will be started. When the
+     * timer expires, Telephony will send the event
      * {@link TelephonyManager#EVENT_DISPLAY_EMERGENCY_MESSAGE} to Dialer, which will then prompt
-     * users to switch to using satellite emergency messaging.
+     * users to switch to using satellite emergency messaging.</li>
+     * </ul>
      * <p>
      * The default value is 300 seconds.
      */
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 60cc9c7..df349f8 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -3556,4 +3556,29 @@
         }
         return smscUri;
     }
+
+    /**
+     * Gets the message size of a WAP from the cache.
+     *
+     * @param locationUrl the location to use as a key for looking up the size in the cache.
+     * The locationUrl may or may not have the transactionId appended to the url.
+     *
+     * @return long representing the message size
+     * @throws java.util.NoSuchElementException if the WAP push doesn't exist in the cache
+     * @throws IllegalArgumentException if the locationUrl is empty
+     *
+     * @hide
+     */
+    public long getWapMessageSize(@NonNull String locationUrl) {
+        try {
+            ISms iSms = getISmsService();
+            if (iSms != null) {
+                return iSms.getWapMessageSize(locationUrl);
+            } else {
+                throw new RuntimeException("Could not acquire ISms service.");
+            }
+        } catch (RemoteException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
 }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 55a3da5..f8166e5 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1207,7 +1207,8 @@
      * The dialer app receives this event via
      * {@link Call.Callback#onConnectionEvent(Call, String, Bundle)}.
      * <p>
-     * The {@link Bundle} parameter is expected to include the following extras:
+     * The {@link Bundle} parameter is guaranteed to include the following extras if the below
+     * conditions are met:
      * <ul>
      *     <li>{@link #EXTRA_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE} - the recommending handover
      *         type.</li>
diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java
index 2172d7d..71bb329 100644
--- a/telephony/java/android/telephony/ims/ImsMmTelManager.java
+++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java
@@ -19,6 +19,7 @@
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresFeature;
@@ -45,6 +46,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.IIntegerConsumer;
 import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.flags.Flags;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -447,6 +449,114 @@
     }
 
     /**
+     * Registers a {@link RegistrationCallback} with the system, which will provide IMS emergency
+     * registration updates for the subscription specified in
+     * {@link ImsManager#getImsMmTelManager(int)}. Use
+     * {@link SubscriptionManager.OnSubscriptionsChangedListener} to listen to Subscription changed
+     * events and call {@link #unregisterImsRegistrationCallback(RegistrationCallback)} to clean up.
+     *
+     * When the callback is registered, it will initiate the callback c to be called with the
+     * current emergency registration state.
+     * Emergency registration callback is available when there is valid SIM card.
+     * <p>This API requires one of the following:
+     * <ul>
+     *     <li>The caller holds the READ_PRECISE_PHONE_STATE permission.</li>
+     *     <li>If the caller is the device or profile owner, the caller holds the
+     *     {@link Manifest.permission#READ_PRECISE_PHONE_STATE} permission.</li>
+     *     <li>The caller has carrier privileges (see
+     *     {@link android.telephony.TelephonyManager#hasCarrierPrivileges}) on any
+     *     active subscription.</li>
+     * </ul>
+     * <p>The profile owner is an app that owns a managed profile on the device; for more details
+     * see <a href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
+     * Access by profile owners is deprecated and will be removed in a future release.
+     *
+     * @param executor The executor the callback events should be run on.
+     * @param c The {@link RegistrationCallback} to be added.
+     * @see #unregisterImsEmergencyRegistrationCallback
+     * @throws ImsException if the subscription associated with this callback is valid, but
+     * the {@link ImsService} associated with the subscription is not available. This can happen if
+     * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
+     * reason.
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_EMERGENCY_REGISTRATION_STATE)
+    public void registerImsEmergencyRegistrationCallback(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull RegistrationManager.RegistrationCallback c) throws ImsException {
+        if (c == null) {
+            throw new IllegalArgumentException("Must include a non-null RegistrationCallback.");
+        }
+        if (executor == null) {
+            throw new IllegalArgumentException("Must include a non-null Executor.");
+        }
+        c.setExecutor(executor);
+
+        ITelephony iTelephony = getITelephony();
+        if (iTelephony == null) {
+            throw new ImsException("Could not find Telephony Service.",
+                    ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+        }
+
+        try {
+            iTelephony.registerImsEmergencyRegistrationCallback(mSubId, c.getBinder());
+        } catch (ServiceSpecificException e) {
+            throw new ImsException(e.getMessage(), e.errorCode);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }  catch (IllegalStateException e) {
+            throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+        }
+    }
+
+    /**
+     * Removes an existing {@link RegistrationCallback} for Emergency IMS registration.
+     *
+     * When the subscription associated with this callback is removed (SIM removed, ESIM swap,
+     * etc...), this callback will automatically be removed. If this method is called for an
+     * inactive subscription, it will result in a no-op.
+     * <p>This API requires one of the following:
+     * <ul>
+     *     <li>The caller holds the READ_PRECISE_PHONE_STATE permission.</li>
+     *     <li>If the caller is the device or profile owner, the caller holds the
+     *     {@link Manifest.permission#READ_PRECISE_PHONE_STATE} permission.</li>
+     *     <li>The caller has carrier privileges (see
+     *     {@link android.telephony.TelephonyManager#hasCarrierPrivileges}) on any
+     *     active subscription.</li>
+     * </ul>
+     * <p>The profile owner is an app that owns a managed profile on the device; for more details
+     * see <a href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
+     * Access by profile owners is deprecated and will be removed in a future release.
+     *
+     * @param c The {@link RegistrationCallback} to be removed.
+     * @see android.telephony.SubscriptionManager.OnSubscriptionsChangedListener
+     * @see #registerImsEmergencyRegistrationCallback(Executor,
+     *                                 RegistrationManager.RegistrationCallback)
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_EMERGENCY_REGISTRATION_STATE)
+    public void unregisterImsEmergencyRegistrationCallback(
+            @NonNull RegistrationManager.RegistrationCallback c) {
+        if (c == null) {
+            throw new IllegalArgumentException("Must include a non-null RegistrationCallback.");
+        }
+
+        ITelephony iTelephony = getITelephony();
+        if (iTelephony == null) {
+            Log.w("ImsMmTelManager", "Could not find Telephony Service.");
+            return;
+        }
+
+        try {
+            iTelephony.unregisterImsEmergencyRegistrationCallback(mSubId, c.getBinder());
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
      * {@inheritDoc}
      * @hide
      */
diff --git a/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java b/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java
index 50c438c..f548dba 100644
--- a/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java
+++ b/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java
@@ -16,6 +16,7 @@
 
 package android.telephony.ims;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -26,6 +27,8 @@
 import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.util.ArraySet;
 
+import com.android.internal.telephony.flags.Flags;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -46,12 +49,36 @@
      *
      */
     public static final int ATTR_EPDG_OVER_CELL_INTERNET = 1 << 0;
+    /**
+     * Attribute to specify if ims registration is of type normal or emergency.
+     * <p>
+     *     For emergency registration bit will be set.
+     *     For normal registration bit will not be set.
+     *     This flag is only applicable when listening to emergency IMS registration state updates
+     *     via the ImsMmTelManager#registerImsEmergencyRegistrationCallback API
+     * </p>
+     */
+    @FlaggedApi(Flags.FLAG_EMERGENCY_REGISTRATION_STATE)
+    public static final int ATTR_REGISTRATION_TYPE_EMERGENCY = 1 << 1;
+    /**
+     * Attribute to specify if virtual registration is required.
+     * <p>
+     *     If emergency registration is not required for making emergency call, in such cases
+     *     bit will be set and callback will represent virtual registration status update.
+     *     This flag is only applicable when listening to emergency IMS registration state updates
+     *     via the ImsMmTelManager#registerImsEmergencyRegistrationCallback API
+     * </p>
+     */
+    @FlaggedApi(Flags.FLAG_EMERGENCY_REGISTRATION_STATE)
+    public static final int ATTR_VIRTUAL_FOR_ANONYMOUS_EMERGENCY_CALL = 1 << 2;
 
     /** @hide */
     // Defines the underlying radio technology type that we have registered for IMS over.
     @IntDef(prefix = "ATTR_",
             value = {
                     ATTR_EPDG_OVER_CELL_INTERNET,
+                    ATTR_REGISTRATION_TYPE_EMERGENCY,
+                    ATTR_VIRTUAL_FOR_ANONYMOUS_EMERGENCY_CALL,
             },
             flag = true)
     @Retention(RetentionPolicy.SOURCE)
@@ -67,6 +94,8 @@
         private Set<String> mFeatureTags = Collections.emptySet();
         private @Nullable SipDetails mSipDetails;
 
+        private @ImsAttributeFlag int mAttributeFlags;
+
         /**
          * Build a new instance of {@link ImsRegistrationAttributes}.
          *
@@ -74,6 +103,9 @@
          */
         public Builder(@ImsRegistrationImplBase.ImsRegistrationTech int registrationTech) {
             mRegistrationTech = registrationTech;
+            if (registrationTech == ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM) {
+                mAttributeFlags |= ATTR_EPDG_OVER_CELL_INTERNET;
+            }
         }
 
         /**
@@ -110,25 +142,32 @@
         }
 
         /**
+         * Set the attribute flag ATTR_REGISTRATION_TYPE_EMERGENCY.
+         */
+        @FlaggedApi(Flags.FLAG_EMERGENCY_REGISTRATION_STATE)
+        public @NonNull Builder setFlagRegistrationTypeEmergency() {
+            mAttributeFlags |= ATTR_REGISTRATION_TYPE_EMERGENCY;
+            return this;
+        }
+
+        /**
+         * Set the attribute flag ATTR_VIRTUAL_FOR_ANONYMOUS_EMERGENCY_CALL.
+         */
+        @FlaggedApi(Flags.FLAG_EMERGENCY_REGISTRATION_STATE)
+        public @NonNull Builder setFlagVirtualRegistrationForEmergencyCall() {
+            mAttributeFlags |= ATTR_VIRTUAL_FOR_ANONYMOUS_EMERGENCY_CALL;
+            return this;
+        }
+
+        /**
          * @return A new instance created from this builder.
          */
         public @NonNull ImsRegistrationAttributes build() {
             return new ImsRegistrationAttributes(mRegistrationTech,
                     RegistrationManager.getAccessType(mRegistrationTech),
-                    getAttributeFlags(mRegistrationTech),
+                    mAttributeFlags,
                     mFeatureTags, mSipDetails);
         }
-
-        /**
-         * @return attribute flags from the registration technology.
-         */
-        private static int getAttributeFlags(int imsRadioTech) {
-            int attributes = 0;
-            if (imsRadioTech == ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM) {
-                attributes |= ATTR_EPDG_OVER_CELL_INTERNET;
-            }
-            return attributes;
-        }
     }
 
     private final int mRegistrationTech;
diff --git a/telephony/java/android/telephony/ims/aidl/IImsRegistration.aidl b/telephony/java/android/telephony/ims/aidl/IImsRegistration.aidl
index 219c9c8..2a6b8f2 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsRegistration.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsRegistration.aidl
@@ -28,6 +28,8 @@
    int getRegistrationTechnology();
    oneway void addRegistrationCallback(IImsRegistrationCallback c);
    oneway void removeRegistrationCallback(IImsRegistrationCallback c);
+   oneway void addEmergencyRegistrationCallback(IImsRegistrationCallback c);
+   oneway void removeEmergencyRegistrationCallback(IImsRegistrationCallback c);
    oneway void triggerFullNetworkRegistration(int sipCode, String sipReason);
    oneway void triggerUpdateSipDelegateRegistration();
    oneway void triggerSipDelegateDeregistration();
diff --git a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
index ff378ba..99c26b0 100644
--- a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
@@ -16,6 +16,7 @@
 
 package android.telephony.ims.stub;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -31,6 +32,7 @@
 import android.telephony.ims.aidl.IImsRegistrationCallback;
 import android.util.Log;
 
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.util.RemoteCallbackListExt;
 import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.internal.util.ArrayUtils;
@@ -56,7 +58,6 @@
 public class ImsRegistrationImplBase {
 
     private static final String LOG_TAG = "ImsRegistrationImplBase";
-
     /**
      * @hide
      */
@@ -233,6 +234,31 @@
         }
 
         @Override
+        public void addEmergencyRegistrationCallback(IImsRegistrationCallback c)
+                throws RemoteException {
+            AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
+            executeMethodAsync(() -> {
+                try {
+                    ImsRegistrationImplBase.this.addEmergencyRegistrationCallback(c);
+                } catch (RemoteException e) {
+                    exceptionRef.set(e);
+                }
+            }, "addEmergencyRegistrationCallback");
+
+            if (exceptionRef.get() != null) {
+                throw exceptionRef.get();
+            }
+        }
+
+        @Override
+        public void removeEmergencyRegistrationCallback(IImsRegistrationCallback c)
+                throws RemoteException {
+            executeMethodAsync(() ->
+                    ImsRegistrationImplBase.this.removeEmergencyRegistrationCallback(c),
+                    "removeEmergencyRegistrationCallback");
+        }
+
+        @Override
         public void removeRegistrationCallback(IImsRegistrationCallback c) throws RemoteException {
             executeMethodAsync(() -> ImsRegistrationImplBase.this.removeRegistrationCallback(c),
                     "removeRegistrationCallback");
@@ -302,16 +328,23 @@
 
     private final RemoteCallbackListExt<IImsRegistrationCallback> mCallbacks =
             new RemoteCallbackListExt<>();
+    private final RemoteCallbackListExt<IImsRegistrationCallback> mEmergencyCallbacks =
+            new RemoteCallbackListExt<>();
     private final Object mLock = new Object();
     // Locked on mLock
     private ImsRegistrationAttributes mRegistrationAttributes;
+    private ImsRegistrationAttributes mEmergencyRegistrationAttributes;
     // Locked on mLock
     private int mRegistrationState = REGISTRATION_STATE_UNKNOWN;
+    private int mEmergencyRegistrationState = REGISTRATION_STATE_UNKNOWN;
     // Locked on mLock, create unspecified disconnect cause.
     private ImsReasonInfo mLastDisconnectCause = new ImsReasonInfo();
+    private ImsReasonInfo mEmergencyLastDisconnectCause = new ImsReasonInfo();
     // Locked on mLock
     private int mLastDisconnectSuggestedAction = RegistrationManager.SUGGESTED_ACTION_NONE;
+    private int mEmergencyLastDisconnectSuggestedAction = RegistrationManager.SUGGESTED_ACTION_NONE;
     private int mLastDisconnectRadioTech = REGISTRATION_TECH_NONE;
+    private int mEmergencyLastDisconnectRadioTech = REGISTRATION_TECH_NONE;
 
     // We hold onto the uris each time they change so that we can send it to a callback when its
     // first added.
@@ -331,7 +364,7 @@
         // do not want to risk introducing a deadlock by using the same mCallbacks Object to
         // synchronize on outgoing and incoming operations.
         mCallbacks.register(c);
-        updateNewCallbackWithState(c);
+        updateNewCallbackWithState(c, false);
     }
 
     private void removeRegistrationCallback(IImsRegistrationCallback c) {
@@ -342,6 +375,16 @@
         mCallbacks.unregister(c);
     }
 
+    private void addEmergencyRegistrationCallback(IImsRegistrationCallback c)
+            throws RemoteException {
+        mEmergencyCallbacks.register(c);
+        updateNewCallbackWithState(c, true);
+    }
+
+    private void removeEmergencyRegistrationCallback(IImsRegistrationCallback c) {
+        mEmergencyCallbacks.unregister(c);
+    }
+
     /**
      * Called by the framework to request that the ImsService perform the network registration
      * of all SIP delegates associated with this ImsService.
@@ -428,14 +471,19 @@
      */
     @SystemApi
     public final void onRegistered(@NonNull ImsRegistrationAttributes attributes) {
-        updateToState(attributes, RegistrationManager.REGISTRATION_STATE_REGISTERED);
+        boolean isEmergency = isEmergency(attributes);
+        if (isEmergency) {
+            updateToEmergencyState(attributes, RegistrationManager.REGISTRATION_STATE_REGISTERED);
+        } else {
+            updateToState(attributes, RegistrationManager.REGISTRATION_STATE_REGISTERED);
+        }
         broadcastToCallbacksLocked((c) -> {
             try {
                 c.onRegistered(attributes);
             } catch (RemoteException e) {
                 Log.w(LOG_TAG, e + "onRegistered(int, Set) - Skipping callback.");
             }
-        });
+        }, isEmergency);
     }
 
     /**
@@ -457,14 +505,19 @@
      */
     @SystemApi
     public final void onRegistering(@NonNull ImsRegistrationAttributes attributes) {
-        updateToState(attributes, RegistrationManager.REGISTRATION_STATE_REGISTERING);
+        boolean isEmergency = isEmergency(attributes);
+        if (isEmergency) {
+            updateToEmergencyState(attributes, RegistrationManager.REGISTRATION_STATE_REGISTERING);
+        } else {
+            updateToState(attributes, RegistrationManager.REGISTRATION_STATE_REGISTERING);
+        }
         broadcastToCallbacksLocked((c) -> {
             try {
                 c.onRegistering(attributes);
             } catch (RemoteException e) {
                 Log.w(LOG_TAG, e + "onRegistering(int, Set) - Skipping callback.");
             }
-        });
+        }, isEmergency);
     }
 
     /**
@@ -513,16 +566,57 @@
     public final void onDeregistered(@Nullable ImsReasonInfo info,
             @RegistrationManager.SuggestedAction int suggestedAction,
             @ImsRegistrationTech int imsRadioTech) {
-        updateToDisconnectedState(info, suggestedAction, imsRadioTech);
+        // Impl to keep backwards compatibility with old implementations
+        ImsRegistrationAttributes attributes = mRegistrationAttributes != null
+                ? new ImsRegistrationAttributes(imsRadioTech,
+                        mRegistrationAttributes.getTransportType(),
+                        mRegistrationAttributes.getAttributeFlags(),
+                        mRegistrationAttributes.getFeatureTags()) :
+                new ImsRegistrationAttributes.Builder(imsRadioTech).build();
+        onDeregistered(info, suggestedAction, attributes);
+    }
+
+    /**
+     * Notify the framework that the device is disconnected from the IMS network.
+     * <p>
+     * Note: Prior to calling {@link #onDeregistered(ImsReasonInfo,int)}, you should ensure that any
+     * changes to {@link android.telephony.ims.feature.ImsFeature} capability availability is sent
+     * to the framework.  For example,
+     * {@link android.telephony.ims.feature.MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO}
+     * and
+     * {@link android.telephony.ims.feature.MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE}
+     * may be set to unavailable to ensure the framework knows these services are no longer
+     * available due to de-registration.  If you do not report capability changes impacted by
+     * de-registration, the framework will not know which features are no longer available as a
+     * result.
+     *
+     * @param info the {@link ImsReasonInfo} associated with why registration was disconnected.
+     * @param suggestedAction the expected behavior of radio protocol stack.
+     * @param attributes The attributes associated with the IMS registration
+     * @hide This API is not part of the Android public SDK API
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_EMERGENCY_REGISTRATION_STATE)
+    public final void onDeregistered(@Nullable ImsReasonInfo info,
+                                     @RegistrationManager.SuggestedAction int suggestedAction,
+                                     @NonNull ImsRegistrationAttributes attributes) {
+        boolean isEmergency = isEmergency(attributes);
+        int imsRadioTech = attributes.getRegistrationTechnology();
+        if (isEmergency) {
+            updateToDisconnectedEmergencyState(info, suggestedAction, imsRadioTech);
+        } else {
+            updateToDisconnectedState(info, suggestedAction, imsRadioTech);
+        }
         // ImsReasonInfo should never be null.
         final ImsReasonInfo reasonInfo = (info != null) ? info : new ImsReasonInfo();
+
         broadcastToCallbacksLocked((c) -> {
             try {
                 c.onDeregistered(reasonInfo, suggestedAction, imsRadioTech);
             } catch (RemoteException e) {
                 Log.w(LOG_TAG, e + "onDeregistered() - Skipping callback.");
             }
-        });
+        }, isEmergency);
     }
 
     /**
@@ -568,6 +662,7 @@
      *
      * @param info the {@link ImsReasonInfo} associated with why registration was disconnected.
      * @param suggestedAction the expected behavior of radio protocol stack.
+     * @param imsRadioTech the network type on which IMS registration has failed.
      * @param details the {@link SipDetails} related to disconnected Ims registration
      * @hide This API is not part of the Android public SDK API
      */
@@ -584,7 +679,7 @@
             } catch (RemoteException e) {
                 Log.w(LOG_TAG, e + "onDeregistered() - Skipping callback.");
             }
-        });
+        }, false);
     }
 
     /**
@@ -599,14 +694,38 @@
     @SystemApi
     public final void onTechnologyChangeFailed(@ImsRegistrationTech int imsRadioTech,
             ImsReasonInfo info) {
+        ImsRegistrationAttributes attributes = mRegistrationAttributes != null
+                ? new ImsRegistrationAttributes(imsRadioTech,
+                        mRegistrationAttributes.getTransportType(),
+                        mRegistrationAttributes.getAttributeFlags(),
+                        mRegistrationAttributes.getFeatureTags()) :
+                new ImsRegistrationAttributes.Builder(imsRadioTech).build();
+        onTechnologyChangeFailed(info, attributes);
+    }
+
+    /**
+     * Notify the framework that the handover from the current radio technology to the technology
+     * defined in {@code imsRadioTech} has failed.
+     * {@link #REGISTRATION_TECH_LTE}, {@link #REGISTRATION_TECH_IWLAN} and
+     * {@link #REGISTRATION_TECH_CROSS_SIM}.
+     * @param info The {@link ImsReasonInfo} for the failure to change technology.
+     * @param attributes The attributes associated with the IMS registration
+     * @hide This API is not part of the Android public SDK API
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_EMERGENCY_REGISTRATION_STATE)
+    public final void onTechnologyChangeFailed(@Nullable ImsReasonInfo info,
+                                               @NonNull ImsRegistrationAttributes attributes) {
+        boolean isEmergency = isEmergency(attributes);
+        int imsRadioTech = attributes.getRegistrationTechnology();
         final ImsReasonInfo reasonInfo = (info != null) ? info : new ImsReasonInfo();
-        broadcastToCallbacksLocked((c) -> {
+        broadcastToCallbacksLocked(c -> {
             try {
                 c.onTechnologyChangeFailed(imsRadioTech, reasonInfo);
             } catch (RemoteException e) {
                 Log.w(LOG_TAG, e + "onTechnologyChangeFailed() - Skipping callback.");
             }
-        });
+        }, isEmergency);
     }
 
     /**
@@ -623,19 +742,35 @@
             mUris = ArrayUtils.cloneOrNull(uris);
             mUrisSet = true;
         }
-        broadcastToCallbacksLocked((c) -> onSubscriberAssociatedUriChanged(c, uris));
+        broadcastToCallbacksLocked((c) -> onSubscriberAssociatedUriChanged(c, uris), false);
+    }
+
+    private boolean isEmergency(ImsRegistrationAttributes attributes) {
+        if (attributes == null) {
+            return false;
+        } else {
+            return (attributes.getAttributeFlags()
+                    & ImsRegistrationAttributes.ATTR_REGISTRATION_TYPE_EMERGENCY) != 0;
+        }
     }
 
     /**
-     * Broadcast the specified operation in a synchronized manner so that multiple threads do not
+     * Broadcast the specified operation in ta synchronized manner so that multiple threads do not
      * try to call broadcast at the same time, which will generate an error.
      * @param c The Consumer lambda method containing the callback to call.
      */
-    private void broadcastToCallbacksLocked(Consumer<IImsRegistrationCallback> c) {
+    private void broadcastToCallbacksLocked(Consumer<IImsRegistrationCallback> c,
+                                            boolean isEmergency) {
         // One broadcast can happen at a time, so synchronize threads so only one
         // beginBroadcast/endBroadcast happens at a time.
-        synchronized (mCallbacks) {
-            mCallbacks.broadcastAction(c);
+        if (isEmergency) {
+            synchronized (mEmergencyCallbacks) {
+                mEmergencyCallbacks.broadcastAction(c);
+            }
+        } else {
+            synchronized (mCallbacks) {
+                mCallbacks.broadcastAction(c);
+            }
         }
     }
 
@@ -657,6 +792,16 @@
         }
     }
 
+    private void updateToEmergencyState(ImsRegistrationAttributes attributes, int newState) {
+        synchronized (mLock) {
+            mEmergencyRegistrationAttributes = attributes;
+            mEmergencyRegistrationState = newState;
+            mEmergencyLastDisconnectCause = null;
+            mEmergencyLastDisconnectSuggestedAction = RegistrationManager.SUGGESTED_ACTION_NONE;
+            mEmergencyLastDisconnectRadioTech = REGISTRATION_TECH_NONE;
+        }
+    }
+
     private void updateToDisconnectedState(ImsReasonInfo info,
             @RegistrationManager.SuggestedAction int suggestedAction,
             @ImsRegistrationTech int imsRadioTech) {
@@ -678,11 +823,33 @@
         }
     }
 
+    private void updateToDisconnectedEmergencyState(ImsReasonInfo info,
+                 @RegistrationManager.SuggestedAction int suggestedAction,
+                 @ImsRegistrationTech int imsRadioTech) {
+        synchronized (mLock) {
+            //We don't want to send this info over if we are disconnected
+            mUrisSet = false;
+            mUris = null;
+
+            updateToEmergencyState(new ImsRegistrationAttributes.Builder(REGISTRATION_TECH_NONE)
+                            .build(),
+                    RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED);
+            if (info != null) {
+                mEmergencyLastDisconnectCause = info;
+                mEmergencyLastDisconnectSuggestedAction = suggestedAction;
+                mEmergencyLastDisconnectRadioTech = imsRadioTech;
+            } else {
+                Log.w(LOG_TAG, "updateToDisconnectedState: no ImsReasonInfo provided.");
+                mEmergencyLastDisconnectCause = new ImsReasonInfo();
+            }
+        }
+    }
+
     /**
      * @param c the newly registered callback that will be updated with the current registration
      *         state.
      */
-    private void updateNewCallbackWithState(IImsRegistrationCallback c)
+    private void updateNewCallbackWithState(IImsRegistrationCallback c, boolean isEmergencyCallback)
             throws RemoteException {
         int state;
         ImsRegistrationAttributes attributes;
@@ -692,11 +859,15 @@
         boolean urisSet;
         Uri[] uris;
         synchronized (mLock) {
-            state = mRegistrationState;
-            attributes = mRegistrationAttributes;
-            disconnectInfo = mLastDisconnectCause;
-            suggestedAction = mLastDisconnectSuggestedAction;
-            imsDisconnectRadioTech = mLastDisconnectRadioTech;
+            state = isEmergencyCallback ? mEmergencyRegistrationState : mRegistrationState;
+            attributes = isEmergencyCallback ? mEmergencyRegistrationAttributes :
+                    mRegistrationAttributes;
+            disconnectInfo = isEmergencyCallback ? mEmergencyLastDisconnectCause :
+                    mLastDisconnectCause;
+            suggestedAction = isEmergencyCallback ? mEmergencyLastDisconnectSuggestedAction :
+                    mLastDisconnectSuggestedAction;
+            imsDisconnectRadioTech = isEmergencyCallback ? mEmergencyLastDisconnectRadioTech :
+                    mLastDisconnectRadioTech;
             urisSet = mUrisSet;
             uris = mUris;
         }
diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl
index 0e23f36..799ac64 100644
--- a/telephony/java/com/android/internal/telephony/ISms.aidl
+++ b/telephony/java/com/android/internal/telephony/ISms.aidl
@@ -592,4 +592,17 @@
      * @hide
      */
     boolean resetAllCellBroadcastRanges(int subId);
+
+    /**
+     * Gets the message size of a WAP from the cache.
+     *
+     * @param locationUrl the location to use as a key for looking up the size in the cache.
+     * The locationUrl may or may not have the transactionId appended to the url.
+     *
+     * @return long representing the message size
+     * @throws java.util.NoSuchElementException if the WAP push doesn't exist in the cache
+     *
+     * @hide
+     */
+    long getWapMessageSize(String locationUrl);
 }
diff --git a/telephony/java/com/android/internal/telephony/ISmsImplBase.java b/telephony/java/com/android/internal/telephony/ISmsImplBase.java
index 6864556..70bc4dd 100644
--- a/telephony/java/com/android/internal/telephony/ISmsImplBase.java
+++ b/telephony/java/com/android/internal/telephony/ISmsImplBase.java
@@ -227,4 +227,10 @@
     public boolean resetAllCellBroadcastRanges(int subId) {
         throw new UnsupportedOperationException();
     }
+
+    @Override
+    public long getWapMessageSize(String locationUrl) {
+        throw new UnsupportedOperationException();
+    }
+
 }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 37752d0..84777c9 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1901,6 +1901,16 @@
     void unregisterImsRegistrationCallback(int subId, IImsRegistrationCallback c);
 
     /**
+     * Adds an IMS emergency registration status callback for the subscription id specified.
+     */
+    void registerImsEmergencyRegistrationCallback(int subId, IImsRegistrationCallback c);
+    /**
+     * Removes an existing IMS emergency registration status callback for the subscription
+     * id specified.
+     */
+    void unregisterImsEmergencyRegistrationCallback(int subId, IImsRegistrationCallback c);
+
+    /**
      * Get the IMS service registration state for the MmTelFeature associated with this sub id.
      */
     void getImsMmTelRegistrationState(int subId, IIntegerConsumer consumer);
@@ -3035,8 +3045,8 @@
      *
      * @param handoverType The type of handover from emergency call to satellite messaging. Use one
      *                     of the following values to enable the override:
-     *                     0 - EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS
-     *                     1 - EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911
+     *                     1 - EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS
+     *                     2 - EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911
      *                     To disable the override, use -1 for handoverType.
      * @param delaySeconds The event EVENT_DISPLAY_EMERGENCY_MESSAGE will be sent to Dialer
      *                     delaySeconds after the emergency call starts.
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 5059017..b7dd4df 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -548,6 +548,7 @@
     int RIL_REQUEST_SET_CELLULAR_IDENTIFIER_DISCLOSED_ENABLED = 247;
     int RIL_REQUEST_SET_SECURITY_ALGORITHMS_UPDATED_ENABLED = 248;
     int RIL_REQUEST_IS_SECURITY_ALGORITHMS_UPDATED_ENABLED = 249;
+    int RIL_REQUEST_GET_SIMULTANEOUS_CALLING_SUPPORT = 250;
 
     /* Responses begin */
     int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
@@ -625,4 +626,5 @@
     int RIL_UNSOL_CONNECTION_SETUP_FAILURE = 1108;
     int RIL_UNSOL_NOTIFY_ANBR = 1109;
     int RIL_UNSOL_IMEI_MAPPING_CHANGED = 1110;
+    int RIL_UNSOL_SIMULTANEOUS_CALLING_SUPPORT_CHANGED = 1111;
 }
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
index c49f8fe..be47ec7 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
@@ -19,12 +19,12 @@
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
+import android.tools.common.flicker.subject.layers.LayersTraceSubject.Companion.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.traces.parsers.toFlickerComponent
-import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ImeAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
@@ -58,9 +58,10 @@
         }
         transitions {
             broadcastActionTrigger.doAction(ACTION_FINISH_ACTIVITY)
-            wmHelper.StateSyncBuilder()
-                    .withActivityRemoved(ActivityOptions.Ime.Default.COMPONENT.toFlickerComponent())
-                    .waitForAndVerify()
+            wmHelper
+                .StateSyncBuilder()
+                .withActivityRemoved(ActivityOptions.Ime.Default.COMPONENT.toFlickerComponent())
+                .waitForAndVerify()
         }
         teardown { simpleApp.exit(wmHelper) }
     }
@@ -69,10 +70,16 @@
 
     @Presubmit @Test fun imeLayerBecomesInvisible() = flicker.imeLayerBecomesInvisible()
 
-    @FlakyTest(bugId = 246284124)
+    @Presubmit
     @Test
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
-        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+        flicker.assertLayers {
+            this.visibleLayersShownMoreThanOneConsecutiveEntry(
+                VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS.toMutableList().also {
+                    it.add(simpleApp.componentMatcher)
+                }
+            )
+        }
     }
 
     @Presubmit
diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp
index 0e2e158..4eac361 100644
--- a/tools/hoststubgen/hoststubgen/Android.bp
+++ b/tools/hoststubgen/hoststubgen/Android.bp
@@ -268,6 +268,9 @@
     srcs: [
         "helper-framework-runtime-src/**/*.java",
     ],
+    exclude_srcs: [
+        "helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/CursorWindow_host.java",
+    ],
     libs: [
         "hoststubgen-helper-runtime",
         "framework-all-hidden-api-host-impl",
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/CursorWindow_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/CursorWindow_host.java
new file mode 100644
index 0000000..631fc02
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/CursorWindow_host.java
@@ -0,0 +1,159 @@
+/*
+ * 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.hoststubgen.nativesubstitution;
+
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.util.Base64;
+
+import java.text.DecimalFormat;
+import java.text.ParsePosition;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+public class CursorWindow_host {
+
+    private static final HashMap<Long, CursorWindow_host> sInstances = new HashMap<>();
+    private static long sNextId = 1;
+
+    private int mColumnNum;
+    private static class Row {
+        String[] fields;
+        int[] types;
+    }
+
+    private final List<Row> mRows = new ArrayList<>();
+
+    public static long nativeCreate(String name, int cursorWindowSize) {
+        CursorWindow_host instance = new CursorWindow_host();
+        long instanceId = sNextId++;
+        sInstances.put(instanceId, instance);
+        return instanceId;
+    }
+
+    public static void nativeDispose(long windowPtr) {
+        sInstances.remove(windowPtr);
+    }
+
+    public static boolean nativeSetNumColumns(long windowPtr, int columnNum) {
+        sInstances.get(windowPtr).mColumnNum = columnNum;
+        return true;
+    }
+
+    public static int nativeGetNumRows(long windowPtr) {
+        return sInstances.get(windowPtr).mRows.size();
+    }
+
+    public static boolean nativeAllocRow(long windowPtr) {
+        CursorWindow_host instance = sInstances.get(windowPtr);
+        Row row = new Row();
+        row.fields = new String[instance.mColumnNum];
+        row.types = new int[instance.mColumnNum];
+        Arrays.fill(row.types, Cursor.FIELD_TYPE_NULL);
+        instance.mRows.add(row);
+        return true;
+    }
+
+    private static boolean put(long windowPtr, String value, int type, int row, int column) {
+        CursorWindow_host instance = sInstances.get(windowPtr);
+        if (row >= instance.mRows.size() || column >= instance.mColumnNum) {
+            return false;
+        }
+        Row r = instance.mRows.get(row);
+        r.fields[column] = value;
+        r.types[column] = type;
+        return true;
+    }
+
+    public static int nativeGetType(long windowPtr, int row, int column) {
+        CursorWindow_host instance = sInstances.get(windowPtr);
+        if (row >= instance.mRows.size() || column >= instance.mColumnNum) {
+            return Cursor.FIELD_TYPE_NULL;
+        }
+
+        return instance.mRows.get(row).types[column];
+    }
+
+    public static boolean nativePutString(long windowPtr, String value,
+            int row, int column) {
+        return put(windowPtr, value, Cursor.FIELD_TYPE_STRING, row, column);
+    }
+
+    public static String nativeGetString(long windowPtr, int row, int column) {
+        CursorWindow_host instance = sInstances.get(windowPtr);
+        if (row >= instance.mRows.size() || column >= instance.mColumnNum) {
+            return null;
+        }
+
+        return instance.mRows.get(row).fields[column];
+    }
+
+    public static boolean nativePutLong(long windowPtr, long value, int row, int column) {
+        return put(windowPtr, Long.toString(value), Cursor.FIELD_TYPE_INTEGER, row, column);
+    }
+
+    public static long nativeGetLong(long windowPtr, int row, int column) {
+        String value = nativeGetString(windowPtr, row, column);
+        if (value == null) {
+            return 0;
+        }
+
+        Number number = new DecimalFormat().parse(value, new ParsePosition(0));
+        return number == null ? 0 : number.longValue();
+    }
+
+    public static boolean nativePutDouble(long windowPtr, double value, int row, int column) {
+        return put(windowPtr, Double.toString(value), Cursor.FIELD_TYPE_FLOAT, row, column);
+    }
+
+    public static double nativeGetDouble(long windowPtr, int row, int column) {
+        String value = nativeGetString(windowPtr, row, column);
+        if (value == null) {
+            return 0;
+        }
+
+        Number number = new DecimalFormat().parse(value, new ParsePosition(0));
+        return number == null ? 0 : number.doubleValue();
+    }
+
+    public static boolean nativePutBlob(long windowPtr, byte[] value, int row, int column) {
+        return put(windowPtr, value == null ? null : Base64.encodeToString(value, 0),
+                Cursor.FIELD_TYPE_BLOB, row, column);
+    }
+
+    public static byte[] nativeGetBlob(long windowPtr, int row, int column) {
+        int type = nativeGetType(windowPtr, row, column);
+        switch (type) {
+            case Cursor.FIELD_TYPE_BLOB: {
+                String value = nativeGetString(windowPtr, row, column);
+                return value == null ? null : Base64.decode(value, 0);
+            }
+            case Cursor.FIELD_TYPE_STRING: {
+                String value = nativeGetString(windowPtr, row, column);
+                return value == null ? null : value.getBytes();
+            }
+            case Cursor.FIELD_TYPE_FLOAT:
+                throw new SQLiteException();
+            case Cursor.FIELD_TYPE_INTEGER:
+                throw new SQLiteException();
+            case Cursor.FIELD_TYPE_NULL:
+            default:
+                return null;
+        }
+    }
+}